@@ -1,24 +1,28 @@ | |||||
//src\app\(main)\analytics\CostandExpenseReport\page.tsx | //src\app\(main)\analytics\CostandExpenseReport\page.tsx | ||||
import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
import { I18nProvider } from "@/i18n"; | |||||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
import CostandExpenseReportComponent from "@/components/Report/CostandExpenseReport"; | |||||
import { Suspense } from "react"; | |||||
import CostAndExpenseReport from "@/components/CostAndExpenseReport"; | |||||
export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
title: "Cost and Expense Report", | title: "Cost and Expense Report", | ||||
}; | }; | ||||
const CostandExpenseReport: React.FC = () => { | |||||
const CostandExpenseReport: React.FC = async () => { | |||||
const { t } = await getServerI18n("report"); | |||||
return ( | return ( | ||||
<I18nProvider namespaces={["analytics"]}> | |||||
<> | |||||
<Typography variant="h4" marginInlineEnd={2}> | <Typography variant="h4" marginInlineEnd={2}> | ||||
Cost and Expense Report | |||||
{t("Cost and Expense Report")} | |||||
</Typography> | </Typography> | ||||
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}> | |||||
<ProgressCashFlowSearch/> | |||||
</Suspense> */} | |||||
<CostandExpenseReportComponent /> | |||||
</I18nProvider> | |||||
<I18nProvider namespaces={["analytics"]}> | |||||
<Suspense fallback={<CostAndExpenseReport.Loading />}> | |||||
<CostAndExpenseReport /> | |||||
</Suspense> | |||||
</I18nProvider> | |||||
</> | |||||
); | ); | ||||
}; | }; | ||||
export default CostandExpenseReport; | export default CostandExpenseReport; |
@@ -1,7 +1,7 @@ | |||||
"use server"; | "use server"; | ||||
import { serverFetchBlob } from "@/app/utils/fetchUtil"; | import { serverFetchBlob } from "@/app/utils/fetchUtil"; | ||||
import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest, LateStartReportRequest, ProjectResourceOverconsumptionReportRequest, ProjectPandLReportRequest, ProjectCompletionReportRequest, ProjectPotentialDelayReportRequest } from "."; | |||||
import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest, LateStartReportRequest, ProjectResourceOverconsumptionReportRequest, ProjectPandLReportRequest, ProjectCompletionReportRequest, ProjectPotentialDelayReportRequest, CostAndExpenseReportRequest } from "."; | |||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
export interface FileResponse { | export interface FileResponse { | ||||
@@ -109,3 +109,16 @@ export const fetchProjectPandLReport = async (data: ProjectPandLReportRequest) = | |||||
return reportBlob | return reportBlob | ||||
}; | }; | ||||
export const fetchCostAndExpenseReport = async (data: CostAndExpenseReportRequest) => { | |||||
const reportBlob = await serverFetchBlob<FileResponse>( | |||||
`${BASE_API_URL}/reports/costandexpenseReport`, | |||||
{ | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}, | |||||
); | |||||
return reportBlob | |||||
}; | |||||
@@ -88,3 +88,14 @@ export interface ProjectCompletionReportRequest { | |||||
endDate: String; | endDate: String; | ||||
outstanding: Boolean; | outstanding: Boolean; | ||||
} | } | ||||
export interface CostAndExpenseReportFilter { | |||||
team: string[]; | |||||
customer: string[]; | |||||
budgetPercentage: String[]; | |||||
} | |||||
export interface CostAndExpenseReportRequest { | |||||
teamId: number | null; | |||||
clientId: number | null; | |||||
budgetPercentage: string; | |||||
} |
@@ -0,0 +1,85 @@ | |||||
"use client"; | |||||
import { CostAndExpenseReportFilter, CostAndExpenseReportRequest } from "@/app/api/reports"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import SearchBox, { Criterion } from "../SearchBox"; | |||||
import { useMemo } from "react"; | |||||
import { TeamResult } from "@/app/api/team"; | |||||
import { Customer } from "@/app/api/customer"; | |||||
import { fetchCostAndExpenseReport } from "@/app/api/reports/actions"; | |||||
import { downloadFile } from "@/app/utils/commonUtil"; | |||||
interface Props { | |||||
team: TeamResult[]; | |||||
customer: Customer[]; | |||||
} | |||||
type SearchQuery = Partial<Omit<CostAndExpenseReportFilter, "id">>; | |||||
type SearchParamNames = keyof SearchQuery; | |||||
const CostAndExpenseReport: React.FC<Props> = ({ team, customer }) => { | |||||
const { t } = useTranslation("report"); | |||||
const teamCombo = team.map((t) => `${t.name} - ${t.code}`); | |||||
const custCombo = customer.map(c => `${c.name} - ${c.code}`) | |||||
const percentList = [">50%", ">90%"] | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
() => [ | |||||
{ | |||||
label: t("Team"), | |||||
paramName: "team", | |||||
type: "select", | |||||
options: teamCombo, | |||||
needAll: true, | |||||
}, | |||||
{ | |||||
label: t("Client"), | |||||
paramName: "customer", | |||||
type: "select", | |||||
options: custCombo, | |||||
needAll: true, | |||||
}, | |||||
{ | |||||
label: t("Remaining Percentage"), | |||||
paramName: "budgetPercentage", | |||||
type: "select", | |||||
options: percentList, | |||||
needAll: true, | |||||
}, | |||||
], | |||||
[t] | |||||
); | |||||
return ( | |||||
<> | |||||
<SearchBox | |||||
criteria={searchCriteria} | |||||
onSearch={async (query: any) => { | |||||
let index = 0 | |||||
let postData: CostAndExpenseReportRequest = { | |||||
teamId: null, | |||||
clientId: null, | |||||
budgetPercentage: ">50%" | |||||
} | |||||
if (query.team.length > 0 && query.team.toLocaleLowerCase() !== "all") { | |||||
index = teamCombo.findIndex(team => team === query.team) | |||||
postData.teamId = team[index].id | |||||
} | |||||
if (query.customer.length > 0 && query.customer.toLocaleLowerCase() !== "all") { | |||||
index = custCombo.findIndex(customer => customer === query.customer) | |||||
postData.clientId = customer[index].id | |||||
} | |||||
if (query.budgetPercentage.length > 0 && query.budgetPercentage.toLocaleLowerCase() !== "all") { | |||||
postData.budgetPercentage = query.budgetPercentage | |||||
} | |||||
console.log(postData) | |||||
const response = await fetchCostAndExpenseReport(postData) | |||||
if (response) { | |||||
downloadFile(new Uint8Array(response.blobValue), response.filename!!) | |||||
} | |||||
}} | |||||
/> | |||||
</> | |||||
); | |||||
}; | |||||
export default CostAndExpenseReport; |
@@ -0,0 +1,41 @@ | |||||
//src\components\LateStartReportGen\LateStartReportGenLoading.tsx | |||||
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 CostAndExpenseReportLoading: React.FC = () => { | |||||
return ( | |||||
<> | |||||
<Card> | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<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 CostAndExpenseReportLoading; |
@@ -0,0 +1,20 @@ | |||||
import React from "react"; | |||||
import { fetchAllCustomers } from "@/app/api/customer"; | |||||
import { fetchTeam } from "@/app/api/team"; | |||||
import CostAndExpenseReport from "./CostAndExpenseReport"; | |||||
import CostAndExpenseReportLoading from "./CostAndExpenseReportLoading"; | |||||
interface SubComponents { | |||||
Loading: typeof CostAndExpenseReportLoading; | |||||
} | |||||
const CostAndExpenseReportWrapper: React.FC & SubComponents = async () => { | |||||
const customers = await fetchAllCustomers() | |||||
const teams = await fetchTeam () | |||||
return <CostAndExpenseReport team={teams} customer={customers}/> | |||||
}; | |||||
CostAndExpenseReportWrapper.Loading = CostAndExpenseReportLoading; | |||||
export default CostAndExpenseReportWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./CostAndExpenseReportWrapper"; |
@@ -12,8 +12,6 @@ import { downloadFile } from "@/app/utils/commonUtil"; | |||||
import { fetchProjectCompletionReport } from "@/app/api/reports/actions"; | import { fetchProjectCompletionReport } from "@/app/api/reports/actions"; | ||||
interface Props { | interface Props { | ||||
// team: TeamResult[] | |||||
// customer: Customer[] | |||||
} | } | ||||
type SearchQuery = Partial<Omit<ProjectCompletionReportFilter, "id">>; | type SearchQuery = Partial<Omit<ProjectCompletionReportFilter, "id">>; | ||||
@@ -21,8 +19,6 @@ type SearchParamNames = keyof SearchQuery; | |||||
const ProjectCompletionReport: React.FC<Props> = ( | const ProjectCompletionReport: React.FC<Props> = ( | ||||
{ | { | ||||
// team, | |||||
// customer | |||||
} | } | ||||
) => { | ) => { | ||||
const { t } = useTranslation("report"); | const { t } = useTranslation("report"); | ||||