Автор | SHA1 | Повідомлення | Дата |
---|---|---|---|
|
427994cf8d | Merge branch 'David_Branch' | 1 рік тому |
|
0cc7246d60 | Merge commit '959a7684850e853fcf680a76aac5cbae56b58fc3' into David_Branch | 1 рік тому |
|
2519e25c7e | added search function in resource consumption and coming milestones | 1 рік тому |
|
a225a9ee1f | update | 1 рік тому |
|
ef9b229eee | Financial Summary Update | 1 рік тому |
@@ -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> | |||
</> | |||
); | |||
}; | |||
@@ -23,6 +23,10 @@ export interface ClientSubsidiaryProjectResult { | |||
export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, tableSorting:string, subsidiaryId?: number) => { | |||
if (subsidiaryId === 0){ | |||
return serverFetchJson<ClientSubsidiaryProjectResult[]>( | |||
`${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}&tableSorting=${tableSorting}` | |||
); | |||
} else if (subsidiaryId === undefined) { | |||
return serverFetchJson<ClientSubsidiaryProjectResult[]>( | |||
`${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&tableSorting=${tableSorting}` | |||
); | |||
@@ -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} | |||
@@ -117,11 +117,11 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => { | |||
abilities!.includes(ability), | |||
), | |||
children: [ | |||
{ | |||
icon: <SummarizeIcon />, | |||
label: "Financial Summary", | |||
path: "/dashboard/ProjectFinancialSummary", | |||
}, | |||
// { | |||
// icon: <SummarizeIcon />, | |||
// label: "Financial Summary", | |||
// path: "/dashboard/ProjectFinancialSummary", | |||
// }, | |||
{ | |||
icon: <PaymentsIcon />, | |||
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 | |||
// { | |||
// icon: <RequestQuote />, | |||
@@ -25,6 +25,9 @@ import { useSearchParams } from 'next/navigation'; | |||
import { fetchAllClientSubsidiaryProjects} from "@/app/api/clientprojects/actions"; | |||
// const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | |||
type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>; | |||
type SearchProjectParamNames = keyof SearchProjectQuery; | |||
interface Props { | |||
// clientSubsidiaryProjectResult: ClientSubsidiaryProjectResult[]; | |||
} | |||
@@ -51,6 +54,7 @@ const ProgressByClient: React.FC<Props> = () => { | |||
React.useState("-"); | |||
const [actualManhourSpent, setActualManhourSpent]: any = React.useState("-"); | |||
const [remainedManhour, setRemainedManhour]: any = React.useState("-"); | |||
const [filteredClientSubsidiaryProjectResult, setFilteredClientSubsidiaryProjectResult]:any[] = useState([]); | |||
const [lastUpdate, setLastUpdate]: any = React.useState("-"); | |||
const [dropdownDemo, setDropdownDemo] = useState(""); | |||
const [dateDemo, setDateDemo] = useState(null); | |||
@@ -100,6 +104,7 @@ const ProgressByClient: React.FC<Props> = () => { | |||
Number(customerId),tableSorting,Number(0)) | |||
console.log(clickResult) | |||
setClientSubsidiaryProjectResult(clickResult); | |||
setFilteredClientSubsidiaryProjectResult(clickResult); | |||
} else { | |||
const clickResult = await fetchAllClientSubsidiaryProjects( | |||
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) { | |||
console.error('Error fetching client subsidiary projects:', error); | |||
} | |||
@@ -141,7 +154,13 @@ const ProgressByClient: React.FC<Props> = () => { | |||
fetchData() | |||
}, [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 = [ | |||
{ | |||
@@ -626,11 +645,25 @@ const ProgressByClient: React.FC<Props> = () => { | |||
className="text-slate-500" | |||
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 | |||
style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | |||
> | |||
<CustomDatagrid | |||
rows={clientSubsidiaryProjectResult} | |||
rows={filteredClientSubsidiaryProjectResult} | |||
columns={columns2} | |||
columnWidth={200} | |||
dataGridHeight={300} | |||
@@ -19,9 +19,12 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||
import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; | |||
import { Suspense } from "react"; | |||
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 }); | |||
type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>; | |||
type SearchProjectParamNames = keyof SearchProjectQuery; | |||
const ProgressByTeam: React.FC = () => { | |||
const searchParams = useSearchParams(); | |||
const teamLeadId = searchParams.get('teamLeadId'); | |||
@@ -51,6 +54,7 @@ const ProgressByTeam: React.FC = () => { | |||
const [chartProjectName, setChartProjectName]:any[] = useState([]); | |||
const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); | |||
const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); | |||
const [filteredTeamProjectResult, setFilteredTeamProjectResult]:any[] = useState([]); | |||
const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); | |||
const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); | |||
const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", | |||
@@ -89,6 +93,14 @@ const ProgressByTeam: React.FC = () => { | |||
const recordsPerPage = 10; | |||
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 () => { | |||
console.log(tableSorting) | |||
if (teamLeadId) { | |||
@@ -97,6 +109,7 @@ const ProgressByTeam: React.FC = () => { | |||
Number(teamLeadId),tableSorting) | |||
console.log(clickResult) | |||
setTeamProjectResult(clickResult); | |||
setFilteredTeamProjectResult(clickResult); | |||
} catch (error) { | |||
console.error('Error fetching team projects:', error); | |||
} | |||
@@ -742,11 +755,25 @@ const ProgressByTeam: React.FC = () => { | |||
className="text-slate-500" | |||
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 | |||
style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | |||
> | |||
<CustomDatagrid | |||
rows={teamProjectResult} | |||
rows={filteredTeamProjectResult} | |||
columns={columns2} | |||
columnWidth={200} | |||
dataGridHeight={300} | |||
@@ -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,25 +71,24 @@ 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); | |||
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 cashFlowProject = await fetchProjectsCashFlow(); | |||
console.log(cashFlowProject) | |||
setProjectData(cashFlowProject) | |||
setFilteredResult(cashFlowProject) | |||
} | |||
const fetchChartData = async () => { | |||
const cashFlowMonthlyChartData = await fetchProjectsCashFlowMonthlyChart(selectedProjectIdList,cashFlowYear); | |||
console.log(cashFlowMonthlyChartData) | |||
const monthlyIncome = [] | |||
const cumulativeIncome = [] | |||
const monthlyExpenditure = [] | |||
@@ -165,8 +168,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 +188,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 +212,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", | |||
@@ -751,15 +762,12 @@ const ProjectCashFlow: React.FC = () => { | |||
); | |||
function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean { | |||
console.log(startDate) | |||
console.log(endDate) | |||
if (!startDate || !endDate) { | |||
return false; | |||
} | |||
const dateToCheckObj = new Date(dateToCheck); | |||
const startDateObj = new Date(startDate); | |||
const endDateObj = new Date(endDate); | |||
console.log(dateToCheckObj) | |||
return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj; | |||
} | |||
@@ -774,7 +782,6 @@ const ProjectCashFlow: React.FC = () => { | |||
<SearchBox | |||
criteria={searchCriteria} | |||
onSearch={(query) => { | |||
console.log(query) | |||
setFilteredResult( | |||
projectData.filter( | |||
(cp:any) => | |||
@@ -794,7 +801,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; | |||
@@ -41,6 +45,7 @@ const dataPositiveStyle:any = { color: "#71d19e", textAlign: "right" } | |||
const ProjectFinancialCard: React.FC<Props> = ({ | |||
Title, | |||
TeamId, | |||
TotalActiveProjectNumber, | |||
TotalFees, | |||
TotalBudget, | |||
@@ -55,8 +60,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" | |||
@@ -75,116 +84,233 @@ 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 /> | |||
<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 className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalActiveProjectNumber.toLocaleString()} | |||
</div> | |||
<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 className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<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 className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</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 /> | |||
<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 className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<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 className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<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 className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</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 /> | |||
<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 className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<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> | |||
{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 | |||
className="text-lg font-medium mx-5" | |||
style={CashFlowStatus === "Negative" && dataNegativeStyle || dataPositiveStyle} | |||
style={dataPositiveStyle} | |||
> | |||
{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 /> | |||
</> | |||
)} | |||
<div | |||
className="text-sm mt-2 font-medium mx-5" | |||
style={{ color: "#898d8d" }} | |||
> | |||
{t("Cost Performance Index") + " (CPI)"} | |||
{"(i) " + t("Cost Performance Index") + " (CPI)"} | |||
</div> | |||
{ ( | |||
<> | |||
<div | |||
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} | |||
</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 /> | |||
</> | |||
)} | |||
<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 | |||
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 | |||
className="text-sm mt-2 font-medium mx-5" | |||
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 && ( | |||
<> | |||
<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> | |||
); | |||
}; | |||
@@ -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}`); | |||
}} | |||
> | |||
{params.value} | |||
</div> | |||
), | |||
}, | |||
{ | |||
id: 'customerName', | |||
@@ -323,6 +335,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', | |||
@@ -524,7 +546,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> | |||
@@ -19,10 +19,11 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||
import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; | |||
import { Suspense } from "react"; | |||
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"; | |||
// const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | |||
type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>; | |||
type SearchProjectParamNames = keyof SearchProjectQuery; | |||
interface Props { | |||
projects: TeamProjectResult[]; | |||
} | |||
@@ -44,6 +45,7 @@ const ProjectResourceConsumptionRanking: React.FC = () => { | |||
const [colorArray, setColorArray]: any[] = useState([]); | |||
const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||
const [pieChartColor, setPieChartColor]: any[] = React.useState([]); | |||
const [filteredTeamProjectResult, setFilteredTeamProjectResult]:any[] = useState([]); | |||
const [totalSpentPercentage, setTotalSpentPercentage]: any = React.useState(); | |||
const [projectBudgetManhour, setProjectBudgetManhour]: any = | |||
React.useState("-"); | |||
@@ -117,6 +119,7 @@ const ProjectResourceConsumptionRanking: React.FC = () => { | |||
selectedTeamIdList,tableSorting) | |||
console.log(clickResult) | |||
setTeamProjectResult(clickResult); | |||
setFilteredTeamProjectResult(clickResult); | |||
setTeamProjectColorOrder(colorOrder); | |||
} catch (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( | |||
() => [ | |||
{ label: t("Team Code"), paramName: "teamCode", type: "text" }, | |||
@@ -865,11 +876,25 @@ const ProjectResourceConsumptionRanking: React.FC = () => { | |||
className="text-slate-500" | |||
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 | |||
style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | |||
> | |||
<CustomDatagrid | |||
rows={teamProjectResult} | |||
rows={filteredTeamProjectResult} | |||
columns={columns2} | |||
columnWidth={200} | |||
dataGridHeight={300} | |||
@@ -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": "進行中" | |||
} |