@@ -1,24 +1,28 @@ | |||
//src\app\(main)\analytics\CostandExpenseReport\page.tsx | |||
import { Metadata } from "next"; | |||
import { I18nProvider } from "@/i18n"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
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 = { | |||
title: "Cost and Expense Report", | |||
}; | |||
const CostandExpenseReport: React.FC = () => { | |||
const CostandExpenseReport: React.FC = async () => { | |||
const { t } = await getServerI18n("report"); | |||
return ( | |||
<I18nProvider namespaces={["analytics"]}> | |||
<> | |||
<Typography variant="h4" marginInlineEnd={2}> | |||
Cost and Expense Report | |||
{t("Cost and Expense Report")} | |||
</Typography> | |||
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}> | |||
<ProgressCashFlowSearch/> | |||
</Suspense> */} | |||
<CostandExpenseReportComponent /> | |||
</I18nProvider> | |||
<I18nProvider namespaces={["analytics"]}> | |||
<Suspense fallback={<CostAndExpenseReport.Loading />}> | |||
<CostAndExpenseReport /> | |||
</Suspense> | |||
</I18nProvider> | |||
</> | |||
); | |||
}; | |||
export default CostandExpenseReport; |
@@ -1,7 +1,7 @@ | |||
"use server"; | |||
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"; | |||
export interface FileResponse { | |||
@@ -109,3 +109,16 @@ export const fetchProjectPandLReport = async (data: ProjectPandLReportRequest) = | |||
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; | |||
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"; | |||
interface Props { | |||
// team: TeamResult[] | |||
// customer: Customer[] | |||
} | |||
type SearchQuery = Partial<Omit<ProjectCompletionReportFilter, "id">>; | |||
@@ -21,8 +19,6 @@ type SearchParamNames = keyof SearchQuery; | |||
const ProjectCompletionReport: React.FC<Props> = ( | |||
{ | |||
// team, | |||
// customer | |||
} | |||
) => { | |||
const { t } = useTranslation("report"); | |||