ソースを参照

add: project manhour summary report

remove: old codes
tags/Baseline_180220205_Frontend
MSI\derek 9ヶ月前
コミット
5de969bd59
10個のファイルの変更191行の追加78行の削除
  1. +29
    -0
      src/app/(main)/analytics/ProjectManhourSummaryReport/page.tsx
  2. +1
    -1
      src/app/(main)/dashboard/ProjectFinancialSummaryV2/page.tsx
  3. +0
    -76
      src/app/api/financialsummary/index.ts
  4. +15
    -1
      src/app/api/reports/actions.ts
  5. +10
    -0
      src/app/api/reports/index.ts
  6. +71
    -0
      src/components/GenerateProjectManhourSummaryReport/ProjectManhourSummaryReport.tsx
  7. +41
    -0
      src/components/GenerateProjectManhourSummaryReport/ProjectManhourSummaryReportLoading.tsx
  8. +17
    -0
      src/components/GenerateProjectManhourSummaryReport/ProjectManhourSummaryReportWrapper.tsx
  9. +1
    -0
      src/components/GenerateProjectManhourSummaryReport/index.ts
  10. +6
    -0
      src/components/NavigationContent/NavigationContent.tsx

+ 29
- 0
src/app/(main)/analytics/ProjectManhourSummaryReport/page.tsx ファイルの表示

@@ -0,0 +1,29 @@
import { Metadata } from "next";
import { Suspense } from "react";
import { I18nProvider, getServerI18n } from "@/i18n";
import { fetchProjects } from "@/app/api/projects";
import { Typography } from "@mui/material";
import GenerateProjectManhourSummaryReport from "@/components/GenerateProjectManhourSummaryReport";

export const metadata: Metadata = {
title: "Project Manhour Summary Report",
};

const ProjectManhourSummaryReport: React.FC = async () => {
const { t } = await getServerI18n("reports");

return (
<>
<Typography variant="h4" marginInlineEnd={2}>
{t("Project Manhour Summary Report")}
</Typography>
<I18nProvider namespaces={["report", "common"]}>
<Suspense fallback={<GenerateProjectManhourSummaryReport.Loading />}>
<GenerateProjectManhourSummaryReport />
</Suspense>
</I18nProvider>
</>
);
};

export default ProjectManhourSummaryReport;

+ 1
- 1
src/app/(main)/dashboard/ProjectFinancialSummaryV2/page.tsx ファイルの表示

@@ -1,8 +1,8 @@
import { Metadata } from "next";
import { I18nProvider } from "@/i18n";
import ProjectFinancialSummaryV2 from "@/components/ProjectFinancialSummaryV2";
import { preloadClientProjects } from "@/app/api/clientprojects";
import { searchParamsProps } from "@/app/utils/fetchUtil";
import ProjectFinancialSummaryV2 from "@/components/ProjectFinancialSummaryV2";

export const metadata: Metadata = {
title: "Project Status by Client",


+ 0
- 76
src/app/api/financialsummary/index.ts ファイルの表示

@@ -19,65 +19,6 @@ export interface FinancialSummaryCardResult {
cpi: number;
}

export type FinancialSummaryType = {
team: IndividualTeam;
activeProject: number;
totalFees: number;
totalBudget: number;
cumulativeExpenditure: number;
manpowerExpense: number;
projectExpense: number;
invoicedAmount: number;
nonInvoicedAmount: number;
receivedAmount: number;
cashFlowStatus: String;
costPerformanceIndex: number;
projectedCashFlowStatus: String;
projectedCostPerformanceIndex: number;
}

export type FinancialSummaryByProject = {
// project data
id: number,
projectCode: string,
projectName: string,
custId: number,
totalFee: number,
totalBudget: number,
customerCode: string,
customerName: string,
subsidiaryName: string,
// timesheet data
cumulativeExpenditure: number,
manhourExpense: number,
projectExpense: number,
// invoice data
invoicedAmount: number,
nonInvoicedAmount: number,
receivedAmount: number,
// calculation
cashFlowStatus: string,
projectedCashFlowStatus: string,
cpi: number,
projectedCpi: number,
}

export interface FinancialSummaryByClient {
id: number;
customerName: string;
customerCode: string;
subsidiaryName: string;
totalFee: number,
totalBudget: number,
cumulativeExpenditure: number
manhourExpense: number;
projectExpense: number;
invoicedAmount: number;
nonInvoicedAmount: number
receivedAmount: number;
numberOfRecords: number;
}

export type FinancialByProject = {
id: number,
projectName: string,
@@ -104,23 +45,6 @@ export const fetchFinancialSummaryCard = cache(async () => {
return serverFetchJson<FinancialSummaryCardResult[]>(`${BASE_API_URL}/dashboard/searchFinancialSummaryCard`);
});

export const fetchFinancialSummary = cache(async (endDate: string, teamId: number | null, startDate: string | null) => {
var endpoint = `${BASE_API_URL}/dashboard/getFinancialSummary?endDate=${endDate}`
if (teamId) endpoint += `&teamIds=${teamId}`
if (startDate) endpoint += `&startDate=${startDate}`
return serverFetchJson<FinancialSummaryType[]>(endpoint, {
next: { tags: ["financialSummary"] },
});
})

export const fetchFinancialSummaryByProject = cache(async (endDate: string, teamId: string, startDate?: string ) => {
var endpoint = `${BASE_API_URL}/dashboard/getFinancialSummaryByProject?endDate=${endDate}&teamId=${teamId}`
if (startDate) endpoint += `&startDate=${startDate}`
return serverFetchJson<FinancialSummaryByProject[]>(endpoint, {
next: { tags: ["financialSummaryByProject"] },
});
})

export const fetchFinancialSummaryByProjectV2 = cache(async (teamId: number, endDate: string, startDate: string) => {
var endpoint = `${BASE_API_URL}/dashboard/getFinancialSummary-final?`
if (startDate.length > 0) endpoint += `&startDate=${startDate}`


+ 15
- 1
src/app/api/reports/actions.ts ファイルの表示

@@ -1,7 +1,7 @@
"use server";

import { serverFetchBlob } from "@/app/utils/fetchUtil";
import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest, LateStartReportRequest, ProjectResourceOverconsumptionReportRequest, ProjectPandLReportRequest, ProjectCompletionReportRequest, ProjectPotentialDelayReportRequest, CostAndExpenseReportRequest, CrossTeamChargeReportRequest } from ".";
import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest, LateStartReportRequest, ProjectResourceOverconsumptionReportRequest, ProjectPandLReportRequest, ProjectCompletionReportRequest, ProjectPotentialDelayReportRequest, CostAndExpenseReportRequest, CrossTeamChargeReportRequest, ProjectManhourSummaryReportRequest } from ".";
import { BASE_API_URL } from "@/config/api";

export interface FileResponse {
@@ -133,5 +133,19 @@ export const fetchCrossTeamChargeReport = async (data: CrossTeamChargeReportRequ
},
);

return reportBlob
};

export const fetchProjectManhourSummaryReport = async (data: ProjectManhourSummaryReportRequest) => {
console.log(data)
const reportBlob = await serverFetchBlob<FileResponse>(
`${BASE_API_URL}/reports/ProjectManhourSummary`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
},
);

return reportBlob
};

+ 10
- 0
src/app/api/reports/index.ts ファイルの表示

@@ -126,4 +126,14 @@ export interface CrossTeamChargeReportFilter {
export interface CrossTeamChargeReportRequest {
month: string;
teamId: number | "All";
}
export interface ProjectManhourSummaryReportFilter {
teamId: number;
startDate: string;
endDate: string;
}
export interface ProjectManhourSummaryReportRequest {
teamId: number;
startDate: string;
endDate: string;
}

+ 71
- 0
src/components/GenerateProjectManhourSummaryReport/ProjectManhourSummaryReport.tsx ファイルの表示

@@ -0,0 +1,71 @@
"use client";

import { ProjectManhourSummaryReportFilter } from "@/app/api/reports";
import SearchBox, { Criterion } from "../SearchBox";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { TeamResult } from "@/app/api/team";
import { fetchProjectManhourSummaryReport } from "@/app/api/reports/actions";
import { downloadFile } from "@/app/utils/commonUtil";

interface Props {
teams: TeamResult[]
}

type SearchQuery = Partial<Omit<ProjectManhourSummaryReportFilter, "id">>;
type SearchParamNames = keyof SearchQuery;

const ProjectManhourSummaryReport: React.FC<Props> = ({ teams }) => {
const { t } = useTranslation("report");
const teamsCombo = teams.map((team) => ({label: `${team.code} - ${team.name}`, value: team.id}))

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Start Date"),
paramName: "startDate",
type: "monthYear",
},
{
label: t("End Date"),
paramName: "endDate",
type: "monthYear",
},
{
label: t("Team"),
paramName: "teamId",
type: "autocomplete",
options: teamsCombo,
needAll: false,
},
],
[t]
);
return (
<>
<SearchBox
formType={"download"}
criteria={searchCriteria}
onSearch={async (query: any) => {
console.log(query);
var start = query.startDate
var start = query.startDate
const args = {
teamId: query.teamId,
startDate: query.startDate + "-01",
endDate: query.endDate + "-01",
}
console.log(args)
const response = await fetchProjectManhourSummaryReport(args);//
if (response) {
console.log("asdasdas")
downloadFile(
new Uint8Array(response.blobValue),
response.filename!!
);
}
}}
/>
</>)
}
export default ProjectManhourSummaryReport

+ 41
- 0
src/components/GenerateProjectManhourSummaryReport/ProjectManhourSummaryReportLoading.tsx ファイルの表示

@@ -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 ProjectManhourSummaryReportLoading: 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 ProjectManhourSummaryReportLoading;

+ 17
- 0
src/components/GenerateProjectManhourSummaryReport/ProjectManhourSummaryReportWrapper.tsx ファイルの表示

@@ -0,0 +1,17 @@
import React from "react";
import ProjectManhourSummaryReportLoading from "./ProjectManhourSummaryReportLoading";
import ProjectManhourSummaryReport from "./ProjectManhourSummaryReport";
import { fetchTeam } from "@/app/api/team";

interface SubComponents {
Loading: typeof ProjectManhourSummaryReportLoading;
}

const ProjectManhourSummaryReportWrapper: React.FC & SubComponents = async () => {
const teams = await fetchTeam()
return <ProjectManhourSummaryReport teams={teams}/>
}

ProjectManhourSummaryReportWrapper.Loading = ProjectManhourSummaryReportLoading;

export default ProjectManhourSummaryReportWrapper;

+ 1
- 0
src/components/GenerateProjectManhourSummaryReport/index.ts ファイルの表示

@@ -0,0 +1 @@
export { default } from "./ProjectManhourSummaryReportWrapper";

+ 6
- 0
src/components/NavigationContent/NavigationContent.tsx ファイルの表示

@@ -312,6 +312,12 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => {
abilities!.includes(ability),
),
},
{
icon: <Analytics />,
label: "Project Manhour Summary Report",
path: "/analytics/ProjectManhourSummaryReport",
isHidden: false
},
{
icon: <Analytics />,
label: "Staff Monthly Work Hours Analysis Report",


読み込み中…
キャンセル
保存