@@ -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> | |||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
@@ -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} | ||||
@@ -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,14 +71,18 @@ 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); | |||||
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 () => { | const fetchData = async () => { | ||||
@@ -85,7 +93,6 @@ const ProjectCashFlow: React.FC = () => { | |||||
} | } | ||||
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 = [] | ||||
@@ -122,6 +129,7 @@ const ProjectCashFlow: React.FC = () => { | |||||
} | } | ||||
const fetchReceivableAndExpenditureData = async () => { | const fetchReceivableAndExpenditureData = async () => { | ||||
console.log("s2") | |||||
if (selectedProjectIdList.length === 0) { | if (selectedProjectIdList.length === 0) { | ||||
setReceivedPercentage(0) | setReceivedPercentage(0) | ||||
setInvoicedPercentage(0) | setInvoicedPercentage(0) | ||||
@@ -165,8 +173,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 +193,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 +217,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", | ||||
@@ -791,7 +807,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; | ||||
@@ -37,6 +41,7 @@ interface Props { | |||||
const ProjectFinancialCard: React.FC<Props> = ({ | const ProjectFinancialCard: React.FC<Props> = ({ | ||||
Title, | Title, | ||||
TeamId, | |||||
TotalActiveProjectNumber, | TotalActiveProjectNumber, | ||||
TotalFees, | TotalFees, | ||||
TotalBudget, | TotalBudget, | ||||
@@ -51,8 +56,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" | ||||
@@ -71,72 +80,100 @@ 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 /> | |||||
{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" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Total Active Project")} | |||||
{"(a) " + t("Total Active Project")} | |||||
</div> | </div> | ||||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | ||||
{TotalActiveProjectNumber.toLocaleString()} | {TotalActiveProjectNumber.toLocaleString()} | ||||
</div> | </div> | ||||
<hr /> | <hr /> | ||||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Total Fees")} | |||||
{"(b) " + t("Total Fees")} | |||||
</div> | </div> | ||||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | ||||
{TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
</div> | </div> | ||||
<hr /> | <hr /> | ||||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Total Budget")} | |||||
{"(c) " + t("Total Budget")} | |||||
</div> | </div> | ||||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | ||||
{TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
</div> | </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 /> | <hr /> | ||||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Total Cumulative Expenditure")} | |||||
{"(d) " + t("Total Cumulative Expenditure")} | |||||
</div> | </div> | ||||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | ||||
{TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
</div> | </div> | ||||
<hr /> | <hr /> | ||||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Total Invoiced Amount")} | |||||
{"(e) " + t("Total Invoiced Amount")} | |||||
</div> | </div> | ||||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | ||||
{TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
</div> | </div> | ||||
<hr /> | <hr /> | ||||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Total Un-Invoiced Amount")} | |||||
{"(f) " + t("Total Un-Invoiced Amount")} | |||||
</div> | </div> | ||||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | ||||
{TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
</div> | </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 /> | <hr /> | ||||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Total Received Amount")} | |||||
{"(g) " + t("Total Received Amount")} | |||||
</div> | </div> | ||||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | ||||
{TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
</div> | </div> | ||||
<hr /> | <hr /> | ||||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Cash Flow Status")} | |||||
{"(h) " + t("Cash Flow Status")} | |||||
</div> | </div> | ||||
{CashFlowStatus === "Negative" && ( | {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 /> | <hr /> | ||||
</> | </> | ||||
)} | )} | ||||
@@ -148,6 +185,10 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
> | > | ||||
{t(CashFlowStatus)} | {t(CashFlowStatus)} | ||||
</div> | </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 /> | <hr /> | ||||
</> | </> | ||||
)} | )} | ||||
@@ -155,7 +196,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
className="text-sm mt-2 font-medium ml-5" | className="text-sm mt-2 font-medium ml-5" | ||||
style={{ color: "#898d8d" }} | style={{ color: "#898d8d" }} | ||||
> | > | ||||
{t("Cost Performance Index") + " (CPI)"} | |||||
{"(i) " + t("Cost Performance Index") + " (CPI)"} | |||||
</div> | </div> | ||||
{Number(CostPerformanceIndex) < 1 && ( | {Number(CostPerformanceIndex) < 1 && ( | ||||
<> | <> | ||||
@@ -165,6 +206,9 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
> | > | ||||
{CostPerformanceIndex} | {CostPerformanceIndex} | ||||
</div> | </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 /> | <hr /> | ||||
</> | </> | ||||
)} | )} | ||||
@@ -176,11 +220,14 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
> | > | ||||
{CostPerformanceIndex} | {CostPerformanceIndex} | ||||
</div> | </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 /> | <hr /> | ||||
</> | </> | ||||
)} | )} | ||||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | ||||
{t("Projected Cash Flow Status")} | |||||
{"(j) " + t("Projected Cash Flow Status")} | |||||
</div> | </div> | ||||
{ProjectedCashFlowStatus === "Negative" && ( | {ProjectedCashFlowStatus === "Negative" && ( | ||||
<> | <> | ||||
@@ -190,6 +237,10 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
> | > | ||||
{t(ProjectedCashFlowStatus)} | {t(ProjectedCashFlowStatus)} | ||||
</div> | </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 /> | <hr /> | ||||
</> | </> | ||||
)} | )} | ||||
@@ -208,7 +259,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
className="text-sm mt-2 font-medium ml-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 && ( | {Number(ProjectedCPI) < 1 && ( | ||||
<> | <> | ||||
@@ -218,6 +269,9 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
> | > | ||||
{ProjectedCPI} | {ProjectedCPI} | ||||
</div> | </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 && ( | {Number(ProjectedCPI) >= 1 && ( | ||||
@@ -228,6 +282,9 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
> | > | ||||
{ProjectedCPI} | {ProjectedCPI} | ||||
</div> | </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> | </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}&subsidiaryId=-`); | |||||
}} | |||||
> | |||||
{params.value} | |||||
</div> | |||||
), | |||||
}, | }, | ||||
{ | { | ||||
id: 'customerName', | id: 'customerName', | ||||
@@ -317,6 +329,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', | ||||
@@ -512,7 +534,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> | ||||
@@ -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": "進行中" | |||||
} |