Kaynağa Gözat

Add Staff Last Modified Report

main
MSI\2Fi 3 ay önce
ebeveyn
işleme
9401fd4d30
12 değiştirilmiş dosya ile 232 ekleme ve 10 silme
  1. +28
    -0
      src/app/(main)/analytics/LastModifiedReport/page.tsx
  2. +2
    -2
      src/app/(main)/analytics/ProjectManhourSummaryReport/page.tsx
  3. +2
    -2
      src/app/(main)/analytics/ProjectMonthlyReport/page.tsx
  4. +14
    -1
      src/app/api/reports/actions.ts
  5. +8
    -0
      src/app/api/reports/index.ts
  6. +3
    -0
      src/components/Breadcrumb/Breadcrumb.tsx
  7. +74
    -0
      src/components/GenerateLastModifiedReport/GenerateLastModifiedReport.tsx
  8. +38
    -0
      src/components/GenerateLastModifiedReport/GenerateLastModifiedReportLoading.tsx
  9. +38
    -0
      src/components/GenerateLastModifiedReport/GenerateLastModifiedReportWrapper.tsx
  10. +1
    -0
      src/components/GenerateLastModifiedReport/index.ts
  11. +5
    -1
      src/components/NavigationContent/NavigationContent.tsx
  12. +19
    -4
      src/components/SearchBox/SearchBox.tsx

+ 28
- 0
src/app/(main)/analytics/LastModifiedReport/page.tsx Dosyayı Görüntüle

@@ -0,0 +1,28 @@
import { Metadata } from "next";
import { Suspense } from "react";
import { I18nProvider, getServerI18n } from "@/i18n";
import GenerateLastModifiedReport from "@/components/GenerateLastModifiedReport";
import { Typography } from "@mui/material";

export const metadata: Metadata = {
title: "Staff Last Record Report",
};

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

return (
<>
<Typography variant="h4" marginInlineEnd={2}>
{t("Staff Last Record Report")}
</Typography>
<I18nProvider namespaces={["report", "common"]}>
<Suspense fallback={<GenerateLastModifiedReport.Loading />}>
<GenerateLastModifiedReport />
</Suspense>
</I18nProvider>
</>
);
};

export default LastModifiedReport;

+ 2
- 2
src/app/(main)/analytics/ProjectManhourSummaryReport/page.tsx Dosyayı Görüntüle

@@ -6,7 +6,7 @@ import { Typography } from "@mui/material";
import GenerateProjectManhourSummaryReport from "@/components/GenerateProjectManhourSummaryReport";

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

const ProjectManhourSummaryReport: React.FC = async () => {
@@ -15,7 +15,7 @@ const ProjectManhourSummaryReport: React.FC = async () => {
return (
<>
<Typography variant="h4" marginInlineEnd={2}>
{t("Project Manhour Summary Report")}
{t("Project Manhour Summary Monthly Report")}
</Typography>
<I18nProvider namespaces={["report", "common"]}>
<Suspense fallback={<GenerateProjectManhourSummaryReport.Loading />}>


+ 2
- 2
src/app/(main)/analytics/ProjectMonthlyReport/page.tsx Dosyayı Görüntüle

@@ -5,7 +5,7 @@ import GenerateProjectMonthlyReport from "@/components/GenerateProjectMonthlyRep
import { Typography } from "@mui/material";

export const metadata: Metadata = {
title: "Staff Monthly Work Hours Analysis Report",
title: "Project Manhour Summary Daily Report",
};

const ProjectMonthlyReport: React.FC = async () => {
@@ -14,7 +14,7 @@ const ProjectMonthlyReport: React.FC = async () => {
return (
<>
<Typography variant="h4" marginInlineEnd={2}>
{t("Staff Monthly Work Hours Analysis Report")}
{t("Project Manhour Summary Daily Report")}
</Typography>
<I18nProvider namespaces={["report", "common"]}>
<Suspense fallback={<GenerateProjectMonthlyReport.Loading />}>


+ 14
- 1
src/app/api/reports/actions.ts Dosyayı Görüntüle

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

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

export interface FileResponse {
@@ -163,3 +163,16 @@ export const fetchProjectMonthlyReport = async (data: ProjectMonthlyReportReques

return reportBlob
};

export const fetchLastModifiedReport = async (data: LastModifiedReportRequest) => {
const reportBlob = await serverFetchBlob<FileResponse>(
`${BASE_API_URL}/reports/gen-staff-last-record-report`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
},
);

return reportBlob
};

+ 8
- 0
src/app/api/reports/index.ts Dosyayı Görüntüle

@@ -147,4 +147,12 @@ export interface ProjectMonthlyReportRequest {
export interface ProjectMonthlyReportFilter {
projects: string[];
date: string;
}

export interface LastModifiedReportFilter {
date: string;
}

export interface LastModifiedReportRequest {
dateString: string;
}

+ 3
- 0
src/components/Breadcrumb/Breadcrumb.tsx Dosyayı Görüntüle

@@ -74,6 +74,9 @@ const pathToLabelMap: { [path: string]: string } = {
"/analytics/FinancialStatusReport": "Financial Status Report",
"/analytics/ProjectCashFlowReport": "Project Cash Flow Report",
"/analytics/StaffMonthlyWorkHoursAnalysisReport": "Staff Monthly Work Hours Analysis Report",
"/analytics/ProjectManhourSummaryReport": "Project Manhour Summary Monthly Report",
"/analytics/ProjectMonthlyReport": "Project Manhour Summary Daily Report",
"/analytics/LastModifiedReport": "Staff Last Record Report",
"/analytics/CrossTeamChargeReport": "Cross Team Charge Report",
"/invoice": "Invoice",
};


+ 74
- 0
src/components/GenerateLastModifiedReport/GenerateLastModifiedReport.tsx Dosyayı Görüntüle

@@ -0,0 +1,74 @@
"use client";
import React, { useMemo } from "react";
import SearchBox, { Criterion } from "../SearchBox";
import { useTranslation } from "react-i18next";
import { ProjectResult } from "@/app/api/projects";
import {
fetchLastModifiedReport,
} from "@/app/api/reports/actions";
import { downloadFile } from "@/app/utils/commonUtil";
import { LastModifiedReportFilter } from "@/app/api/reports";


import dayjs from "dayjs";
import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils";

interface Props {
projects: ProjectResult[];
companyHolidays: String[];
}

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

const GenerateLastModifiedReport: React.FC<Props> = ({ projects, companyHolidays }) => {
const { t } = useTranslation("report");
const projectCombo = projects.map((project) => ({label: `${project.code} - ${project.name}`, value: project.id}))
console.log(companyHolidays)

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Date"),
paramName: "date",
type: "date",
},
],
[t]
);
return (
<>
<SearchBox
formType={"download"}
criteria={searchCriteria}
onSearch={async (query: any) => {
console.log(query);
const yearNeeded = parseInt(dayjs(query.date).format("YYYY"))
const holidayList: String[] = [...getPublicHolidaysForNYears(1, yearNeeded).map((item) => dayjs(item.date).format("DD/MM/YYYY")), ...companyHolidays]
const uniqueHoliday = holidayList.filter((value, index, arr) => index === arr.indexOf(value));

let postData = {
dateString: dayjs().format("YYYY-MM-DD").toString(),
holidays: uniqueHoliday
};
console.log(query.date.length > 0)
if (query.date.length > 0) {
postData.dateString = query.date
}
console.log(postData)
const response = await fetchLastModifiedReport(postData);
if (response) {
downloadFile(
new Uint8Array(response.blobValue),
response.filename!!
);
}
}}
/>
</>
);
};

export default GenerateLastModifiedReport;

+ 38
- 0
src/components/GenerateLastModifiedReport/GenerateLastModifiedReportLoading.tsx Dosyayı Görüntüle

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

+ 38
- 0
src/components/GenerateLastModifiedReport/GenerateLastModifiedReportWrapper.tsx Dosyayı Görüntüle

@@ -0,0 +1,38 @@
import React from "react";
import GenerateLastModifiedReportLoading from "./GenerateLastModifiedReportLoading";
import GenerateLastModifiedReport from "./GenerateLastModifiedReport";
import { fetchStaff } from "@/app/api/staff";
import { getServerSession } from "next-auth";
import { authOptions } from "@/config/authConfig";
import { TEAM_LEAD } from "@/middleware";
import { fetchHolidays } from "@/app/api/holidays";
import { convertDateArrayToString } from "@/app/utils/formatUtil";
import { fetchProjects } from "@/app/api/projects";
interface SubComponents {
Loading: typeof GenerateLastModifiedReportLoading;
}

const GenerateLastModifiedReportWrapper: React.FC &
SubComponents = async () => {
const session: any = await getServerSession(authOptions);
const teamId = session.staff?.teamId;
const role = session.role;

const companyHolidays = await fetchHolidays()
let companyHolidaysList: String[] = []
if (companyHolidays.length > 0) {
companyHolidaysList = companyHolidays.map(item => convertDateArrayToString(item.date, "DD/MM/YYYY")) as String[]
}
console.log(companyHolidaysList)
let projects = await fetchProjects();

if (role.includes(TEAM_LEAD)) {
projects = projects.filter((project) => project.teamId === teamId);
}

return <GenerateLastModifiedReport projects={projects} companyHolidays={companyHolidaysList} />;
};

GenerateLastModifiedReportWrapper.Loading = GenerateLastModifiedReportLoading;

export default GenerateLastModifiedReportWrapper;

+ 1
- 0
src/components/GenerateLastModifiedReport/index.ts Dosyayı Görüntüle

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

+ 5
- 1
src/components/NavigationContent/NavigationContent.tsx Dosyayı Görüntüle

@@ -338,7 +338,11 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => {
abilities!.includes(ability),
),
},
{
icon: <Analytics />,
label: "Last Modified Report",
path: "/analytics/LastModifiedReport",
},
],
},
{


+ 19
- 4
src/components/SearchBox/SearchBox.tsx Dosyayı Görüntüle

@@ -72,13 +72,18 @@ interface NumberCriterion<T extends string> extends BaseCriterion<T> {
type: "number";
}

interface DateCriterion<T extends string> extends BaseCriterion<T> {
type: "date";
}

export type Criterion<T extends string> =
| TextCriterion<T>
| SelectCriterion<T>
| AutocompleteCriterion<T>
| DateRangeCriterion<T>
| MonthYearCriterion<T>
| NumberCriterion<T>;
| NumberCriterion<T>
| DateCriterion<T>;

interface Props<T extends string> {
criteria: Criterion<T>[];
@@ -118,6 +123,9 @@ function SearchBox<T extends string>({
case "monthYear":
defaultValue = dayjs().format("YYYY-MM")
break;
case "date":
defaultValue = dayjs().format("YYYY-MM-DD")
break;
}

return {
@@ -185,6 +193,13 @@ function SearchBox<T extends string>({
};
}, []);

const makeSingleDateChangeHandler = useCallback((paramName: T) => {
return (e: any) => {
console.log(dayjs(e).format("YYYY-MM-DD"));
setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") }));
};
}, []);

const makeDateToChangeHandler = useCallback((paramName: T, needMonth?: boolean) => {
return (e: any) => {
if (needMonth) {
@@ -383,7 +398,7 @@ function SearchBox<T extends string>({
value={inputs[c.paramName] && parseFloat(inputs[c.paramName])}
/>
)}
{c.type === "monthYear" && (
{(c.type === "monthYear" || c.type === "date") && (
<LocalizationProvider
dateAdapter={AdapterDayjs}
// TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
@@ -393,13 +408,13 @@ function SearchBox<T extends string>({
<FormControl fullWidth>
<DatePicker
label={c.label}
onChange={makeMonthYearChangeHandler(c.paramName)}
onChange={c.type === "monthYear" ? makeMonthYearChangeHandler(c.paramName) : makeSingleDateChangeHandler(c.paramName)}
value={
inputs[c.paramName]
? dayjs(inputs[c.paramName])
: dayjs()
}
views={["month", "year"]}
views={c.type === "monthYear" ? ["month", "year"] : ["day", "month", "year" ]}
/>
</FormControl>
</Box>


Yükleniyor…
İptal
Kaydet