@@ -0,0 +1,30 @@ | |||||
import { Metadata } from "next"; | |||||
import { Suspense } from "react"; | |||||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
import { fetchProjects } from "@/app/api/projects"; | |||||
import GenerateProjectPandLReport from "@/components/GenerateProjectPandLReport"; | |||||
import { Typography } from "@mui/material"; | |||||
export const metadata: Metadata = { | |||||
title: "Project Cash Flow Report", | |||||
}; | |||||
const ProjectPandLReport: React.FC = async () => { | |||||
const { t } = await getServerI18n("reports"); | |||||
fetchProjects(); | |||||
return ( | |||||
<> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Project P&L Report")} | |||||
</Typography> | |||||
<I18nProvider namespaces={["report", "common"]}> | |||||
<Suspense fallback={<GenerateProjectPandLReport.Loading />}> | |||||
<GenerateProjectPandLReport /> | |||||
</Suspense> | |||||
</I18nProvider> | |||||
</> | |||||
); | |||||
}; | |||||
export default ProjectPandLReport; |
@@ -0,0 +1,63 @@ | |||||
"use client"; | |||||
import React, { useMemo } from "react"; | |||||
import SearchBox, { Criterion } from "../SearchBox"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import { ProjectResult } from "@/app/api/projects"; | |||||
import { ProjectPandLReportFilter } from "@/app/api/reports"; | |||||
import { fetchProjectPandLReport } from "@/app/api/reports/actions"; | |||||
import { downloadFile } from "@/app/utils/commonUtil"; | |||||
import { dateTypeCombo } from "@/app/utils/comboUtil"; | |||||
import { FormHelperText } from "@mui/material"; | |||||
import { errorDialog, errorDialogWithContent } from "../Swal/CustomAlerts"; | |||||
interface Props { | |||||
projects: ProjectResult[]; | |||||
} | |||||
type SearchQuery = Partial<Omit<ProjectPandLReportFilter, "id">>; | |||||
type SearchParamNames = keyof SearchQuery; | |||||
const GenerateProjectPandLReport: React.FC<Props> = ({ projects }) => { | |||||
const { t } = useTranslation("report"); | |||||
const projectCombo = projects.map(project => `${project.code} - ${project.name}`) | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
() => [ | |||||
{ label: t("Project *"), paramName: "project", type: "select", options: projectCombo, needAll: false}, | |||||
{ label: t("Start Month *"), label2: t("End Month *"), paramName: "startMonth", type: "dateRange", needMonth: true }, | |||||
], | |||||
[t], | |||||
); | |||||
return ( | |||||
<> | |||||
<SearchBox | |||||
criteria={searchCriteria} | |||||
onSearch={async (query) => { | |||||
if (query.project.length > 0 && query.project.toLocaleLowerCase() !== "all") { | |||||
const projectIndex = projectCombo.findIndex(project => project === query.project) | |||||
console.log(projects[projectIndex].id, query.startMonth, query.startMonthTo) | |||||
if(projects[projectIndex].id != null && query.startMonth != "" && query.startMonthTo != undefined){ | |||||
const response = await fetchProjectPandLReport({ projectId: projects[projectIndex].id, startMonth: query.startMonth, endMonth: query.startMonthTo }) | |||||
if (response) { | |||||
downloadFile(new Uint8Array(response.blobValue), response.filename!!) | |||||
} | |||||
}else{ | |||||
errorDialogWithContent(t("Download Fail"), | |||||
t(`Please check the required field`), t) | |||||
.then(() => { | |||||
window.location.reload() | |||||
}) | |||||
} | |||||
} | |||||
}} | |||||
formType={"download"} | |||||
/> | |||||
</> | |||||
); | |||||
}; | |||||
export default GenerateProjectPandLReport; |
@@ -0,0 +1,38 @@ | |||||
import Card from "@mui/material/Card"; | |||||
import CardContent from "@mui/material/CardContent"; | |||||
import Skeleton from "@mui/material/Skeleton"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import React from "react"; | |||||
// Can make this nicer | |||||
export const GenerateProjectPandLReportLoading: React.FC = () => { | |||||
return ( | |||||
<> | |||||
<Card> | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton | |||||
variant="rounded" | |||||
height={50} | |||||
width={100} | |||||
sx={{ alignSelf: "flex-end" }} | |||||
/> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
<Card> | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
</> | |||||
); | |||||
}; | |||||
export default GenerateProjectPandLReportLoading; |
@@ -0,0 +1,18 @@ | |||||
import React from "react"; | |||||
import GenerateProjectPandLReportLoading from "./GenerateProjectPandLReportLoading"; | |||||
import { fetchProjects } from "@/app/api/projects"; | |||||
import GenerateProjectPandLReport from "./GenerateProjectPandLReport"; | |||||
interface SubComponents { | |||||
Loading: typeof GenerateProjectPandLReportLoading; | |||||
} | |||||
const GenerateProjectPandLReportWrapper: React.FC & SubComponents = async () => { | |||||
const projects = await fetchProjects(); | |||||
return <GenerateProjectPandLReport projects={projects} />; | |||||
}; | |||||
GenerateProjectPandLReportWrapper.Loading = GenerateProjectPandLReportLoading; | |||||
export default GenerateProjectPandLReportWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./GenerateProjectPandLReportWrapper"; |
@@ -175,7 +175,7 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
{ | { | ||||
icon: <Analytics />, | icon: <Analytics />, | ||||
label: "Project P&L Report", | label: "Project P&L Report", | ||||
path: "/analytics/ProjectPLReport", | |||||
path: "/analytics/ProjectPandLReport", | |||||
}, | }, | ||||
{ | { | ||||
icon: <Analytics />, | icon: <Analytics />, | ||||