@@ -0,0 +1,28 @@ | |||||
import { Metadata } from "next"; | |||||
import { Suspense } from "react"; | |||||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
import GenerateMonthlyWorkHoursReport from "@/components/GenerateMonthlyWorkHoursReport"; | |||||
import { Typography } from "@mui/material"; | |||||
export const metadata: Metadata = { | |||||
title: "Staff Monthly Work Hours Analysis Report", | |||||
}; | |||||
const StaffMonthlyWorkHoursAnalysisReport: React.FC = async () => { | |||||
const { t } = await getServerI18n("User Group"); | |||||
return ( | |||||
<> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Staff Monthly Work Hours Analysis Report")} | |||||
</Typography> | |||||
<I18nProvider namespaces={["report", "common"]}> | |||||
<Suspense fallback={<GenerateMonthlyWorkHoursReport.Loading />}> | |||||
<GenerateMonthlyWorkHoursReport /> | |||||
</Suspense> | |||||
</I18nProvider> | |||||
</> | |||||
); | |||||
}; | |||||
export default StaffMonthlyWorkHoursAnalysisReport; |
@@ -7,21 +7,28 @@ import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
import EditSkill from "@/components/EditSkill"; | import EditSkill from "@/components/EditSkill"; | ||||
import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
import { fetchSkill } from "@/app/api/skill"; | |||||
export interface searchParamsProps { | |||||
searchParams: { [key: string]: string | string[] | undefined }; | |||||
} | |||||
const EditSkillPage: React.FC = async () => { | |||||
const EditSkillPage: React.FC<searchParamsProps> = async ({ | |||||
searchParams, | |||||
}) => { | |||||
console.log(searchParams.id) | |||||
const { t } = await getServerI18n("staff"); | const { t } = await getServerI18n("staff"); | ||||
return ( | return ( | ||||
<> | |||||
<Typography variant="h4">{t("Edit Skill")}</Typography> | |||||
<I18nProvider namespaces={["team", "common"]}> | |||||
<Suspense fallback={<EditSkill.Loading />}> | |||||
<EditSkill /> | |||||
</Suspense> | |||||
</I18nProvider> | |||||
{/* <EditStaff /> */} | |||||
</> | |||||
<> | |||||
<Typography variant="h4">{t("Edit Skill")}</Typography> | |||||
<I18nProvider namespaces={["team", "common"]}> | |||||
<Suspense fallback={<EditSkill.Loading />}> | |||||
<EditSkill id={parseInt(searchParams.id as string)}/> | |||||
</Suspense> | |||||
</I18nProvider> | |||||
</> | |||||
); | ); | ||||
}; | }; | ||||
@@ -4,16 +4,20 @@ import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
import EditUser from "@/components/EditUser"; | import EditUser from "@/components/EditUser"; | ||||
import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
import { searchParamsProps } from "../../skill/edit/page"; | |||||
import { preloadUser } from "@/app/api/user"; | |||||
const User: React.FC = async () => { | |||||
const User: React.FC<searchParamsProps> = async ({ | |||||
searchParams | |||||
}) => { | |||||
const { t } = await getServerI18n("user"); | const { t } = await getServerI18n("user"); | ||||
preloadUser() | |||||
return ( | return ( | ||||
<> | <> | ||||
<Typography variant="h4">{t("Edit User")}</Typography> | <Typography variant="h4">{t("Edit User")}</Typography> | ||||
<I18nProvider namespaces={["user", "common"]}> | <I18nProvider namespaces={["user", "common"]}> | ||||
<Suspense fallback={<EditUser.Loading />}> | <Suspense fallback={<EditUser.Loading />}> | ||||
<EditUser /> | |||||
<EditUser id={parseInt(searchParams.id as string)}/> | |||||
</Suspense> | </Suspense> | ||||
</I18nProvider> | </I18nProvider> | ||||
</> | </> | ||||
@@ -6,15 +6,25 @@ import { I18nProvider } from "@/i18n"; | |||||
// import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper"; | // import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper"; | ||||
import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
import EditUser from "@/components/EditUser"; | import EditUser from "@/components/EditUser"; | ||||
import { useRouter } from 'next/router'; | |||||
import { getServerSideProps } from "next/dist/build/templates/pages"; | |||||
import { searchParamsProps } from "../../skill/edit/page"; | |||||
import { preloadUserDetail } from "@/app/api/user"; | |||||
const EditUserPage: React.FC = () => { | |||||
const EditUserPage: React.FC<searchParamsProps> = async ({ | |||||
searchParams | |||||
}) => { | |||||
const id = parseInt(searchParams.id as string) | |||||
preloadUserDetail(id) | |||||
return ( | return ( | ||||
<> | <> | ||||
<I18nProvider namespaces={["team", "common"]}> | <I18nProvider namespaces={["team", "common"]}> | ||||
<Suspense fallback={<EditUser.Loading />}> | <Suspense fallback={<EditUser.Loading />}> | ||||
<EditUser /> | |||||
<EditUser | |||||
// id={id} | |||||
/> | |||||
</Suspense> | </Suspense> | ||||
</I18nProvider> | </I18nProvider> | ||||
</> | </> | ||||
@@ -29,7 +29,7 @@ export interface record { | |||||
records: auth[]; | records: auth[]; | ||||
} | } | ||||
export const fetchAuth = cache(async (target: string, id?: number) => { | |||||
export const fetchAuth = cache(async (target: string, id?: number ) => { | |||||
return serverFetchJson<record>(`${BASE_API_URL}/group/auth/${target}/${id ?? 0}`, { | return serverFetchJson<record>(`${BASE_API_URL}/group/auth/${target}/${id ?? 0}`, { | ||||
next: { tags: ["auth"] }, | next: { tags: ["auth"] }, | ||||
}); | }); | ||||
@@ -1,7 +1,7 @@ | |||||
"use server"; | "use server"; | ||||
import { serverFetchBlob, serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchBlob, serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
import { ProjectCashFlowReportRequest } from "."; | |||||
import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest } from "."; | |||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
export interface FileResponse { | export interface FileResponse { | ||||
@@ -19,5 +19,18 @@ export const fetchProjectCashFlowReport = async (data: ProjectCashFlowReportRequ | |||||
}, | }, | ||||
); | ); | ||||
return reportBlob | |||||
}; | |||||
export const fetchMonthlyWorkHoursReport = async (data: MonthlyWorkHoursReportRequest) => { | |||||
const reportBlob = await serverFetchBlob<FileResponse>( | |||||
`${BASE_API_URL}/reports/StaffMonthlyWorkHourAnalysisReport`, | |||||
{ | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}, | |||||
); | |||||
return reportBlob | return reportBlob | ||||
}; | }; |
@@ -1,3 +1,5 @@ | |||||
import { records } from "../staff/actions"; | |||||
// - Project Cash Flow Report | // - Project Cash Flow Report | ||||
export interface ProjectCashFlowReportFilter { | export interface ProjectCashFlowReportFilter { | ||||
project: string[]; | project: string[]; | ||||
@@ -5,4 +7,16 @@ export interface ProjectCashFlowReportFilter { | |||||
export interface ProjectCashFlowReportRequest { | export interface ProjectCashFlowReportRequest { | ||||
projectId: number; | projectId: number; | ||||
} | |||||
} | |||||
// - Monthly Work Hours Report | |||||
export interface MonthlyWorkHoursReportFilter { | |||||
staff: string[]; | |||||
date: any; | |||||
} | |||||
export interface MonthlyWorkHoursReportRequest { | |||||
id: number; | |||||
yearMonth: string; | |||||
} | |||||
@@ -19,4 +19,10 @@ export interface SkillResult { | |||||
return serverFetchJson<SkillResult[]>(`${BASE_API_URL}/skill`, { | return serverFetchJson<SkillResult[]>(`${BASE_API_URL}/skill`, { | ||||
next: { tags: ["sill"] }, | next: { tags: ["sill"] }, | ||||
}); | }); | ||||
}); | |||||
export const fetchSkillDetail = cache(async (id: number) => { | |||||
return serverFetchJson<SkillResult[]>(`${BASE_API_URL}/skill/${id}`, { | |||||
next: { tags: ["sill"] }, | |||||
}); | |||||
}); | }); |
@@ -39,9 +39,9 @@ export interface CreateStaffInputs { | |||||
name: string; | name: string; | ||||
// team: Team[]; | // team: Team[]; | ||||
} | } | ||||
export interface Staff4TransferList { | |||||
records: records[]; | |||||
} | |||||
// export interface Staff4TransferList { | |||||
// records: records[]; | |||||
// } | |||||
export const saveStaff = async (data: CreateStaffInputs) => { | export const saveStaff = async (data: CreateStaffInputs) => { | ||||
return serverFetchJson(`${BASE_API_URL}/staffs/save`, { | return serverFetchJson(`${BASE_API_URL}/staffs/save`, { | ||||
@@ -79,7 +79,7 @@ export const fetchStaffEdit = cache(async (id: number) => { | |||||
// }; | // }; | ||||
export const fetchStaffCombo = cache(async () => { | export const fetchStaffCombo = cache(async () => { | ||||
return serverFetchJson<Staff4TransferList>(`${BASE_API_URL}/staffs/combo`, { | |||||
return serverFetchJson<records[]>(`${BASE_API_URL}/staffs/combo`, { | |||||
next: { tags: ["staffs"] }, | next: { tags: ["staffs"] }, | ||||
}); | }); | ||||
}); | }); | ||||
@@ -19,7 +19,6 @@ export interface PasswordInputs { | |||||
newPasswordCheck: string; | newPasswordCheck: string; | ||||
} | } | ||||
export const fetchUserDetails = cache(async (id: number) => { | export const fetchUserDetails = cache(async (id: number) => { | ||||
return serverFetchJson<UserDetail>(`${BASE_API_URL}/user/${id}`, { | return serverFetchJson<UserDetail>(`${BASE_API_URL}/user/${id}`, { | ||||
next: { tags: ["user"] }, | next: { tags: ["user"] }, | ||||
@@ -38,8 +38,18 @@ export interface UserDetail { | |||||
fetchUser(); | fetchUser(); | ||||
}; | }; | ||||
export const preloadUserDetail = (id: number) => { | |||||
fetchUserDetail(id); | |||||
}; | |||||
export const fetchUser = cache(async () => { | export const fetchUser = cache(async () => { | ||||
return serverFetchJson<UserResult[]>(`${BASE_API_URL}/user`, { | return serverFetchJson<UserResult[]>(`${BASE_API_URL}/user`, { | ||||
next: { tags: ["user"] }, | next: { tags: ["user"] }, | ||||
}); | }); | ||||
}); | |||||
export const fetchUserDetail = cache(async (id: number) => { | |||||
return serverFetchJson<UserResult[]>(`${BASE_API_URL}/user/${id}`, { | |||||
next: { tags: ["user"] }, | |||||
}); | |||||
}); | }); |
@@ -11,7 +11,7 @@ interface SubComponents { | |||||
} | } | ||||
const CreateGroupWrapper: React.FC & SubComponents = async () => { | const CreateGroupWrapper: React.FC & SubComponents = async () => { | ||||
const records = await fetchAuth() | |||||
const records = await fetchAuth("group") | |||||
const users = await fetchUser() | const users = await fetchUser() | ||||
console.log(users) | console.log(users) | ||||
const auth = records.records as auth[] | const auth = records.records as auth[] | ||||
@@ -13,7 +13,6 @@ import { | |||||
} from "react-hook-form"; | } from "react-hook-form"; | ||||
// import CreateTeamForm from "../CreateTeamForm"; | // import CreateTeamForm from "../CreateTeamForm"; | ||||
import { CreateTeamInputs } from "@/app/api/team/actions"; | import { CreateTeamInputs } from "@/app/api/team/actions"; | ||||
// import { Staff4TransferList, fetchStaffCombo } from "@/app/api/staff/actions"; | |||||
import { StaffResult } from "@/app/api/staff"; | import { StaffResult } from "@/app/api/staff"; | ||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material"; | import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material"; | ||||
@@ -3,15 +3,18 @@ import EditSkill from "./EditSkill"; | |||||
import EditSkillLoading from "./EditSkillLoading"; | import EditSkillLoading from "./EditSkillLoading"; | ||||
import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; | import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; | ||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
import { fetchSkill } from "@/app/api/skill"; | |||||
import { fetchSkill, fetchSkillDetail } from "@/app/api/skill"; | |||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof EditSkillLoading; | Loading: typeof EditSkillLoading; | ||||
} | } | ||||
const EditSkillWrapper: React.FC & SubComponents = async () => { | |||||
const skills = await fetchSkill() | |||||
console.log(skills) | |||||
interface Props { | |||||
id: number | |||||
} | |||||
const EditSkillWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||||
const skills = await fetchSkillDetail(id) | |||||
return <EditSkill skills={skills}/>; | return <EditSkill skills={skills}/>; | ||||
}; | }; | ||||
@@ -12,7 +12,6 @@ import { | |||||
useFormContext, | useFormContext, | ||||
} from "react-hook-form"; | } from "react-hook-form"; | ||||
import { CreateTeamInputs } from "@/app/api/team/actions"; | import { CreateTeamInputs } from "@/app/api/team/actions"; | ||||
import { Staff4TransferList, fetchStaffCombo } from "@/app/api/staff/actions"; | |||||
import { StaffResult, StaffTeamTable } from "@/app/api/staff"; | import { StaffResult, StaffTeamTable } from "@/app/api/staff"; | ||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material"; | import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material"; | ||||
@@ -5,15 +5,20 @@ import EditUserLoading from "./EditUserLoading"; | |||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
import { fetchTeam, fetchTeamDetail } from "@/app/api/team"; | import { fetchTeam, fetchTeamDetail } from "@/app/api/team"; | ||||
import { fetchStaff } from "@/app/api/staff"; | import { fetchStaff } from "@/app/api/staff"; | ||||
import { fetchUser } from "@/app/api/user"; | |||||
import { fetchUser, fetchUserDetail } from "@/app/api/user"; | |||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof EditUserLoading; | Loading: typeof EditUserLoading; | ||||
} | } | ||||
const EditUserWrapper: React.FC & SubComponents = async () => { | |||||
const users = await fetchUser() | |||||
console.log(users) | |||||
interface Props { | |||||
// id: number | |||||
} | |||||
const EditUserWrapper: React.FC<Props> & SubComponents = async ({ | |||||
// id | |||||
}) => { | |||||
// const users = await fetchUser() | |||||
// const userDetail = await fetchUserDetail(id) | |||||
return <EditUser /> | return <EditUser /> | ||||
}; | }; | ||||
@@ -0,0 +1,55 @@ | |||||
"use client"; | |||||
import React, { useMemo } from "react"; | |||||
import SearchBox, { Criterion } from "../SearchBox"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import { ProjectResult } from "@/app/api/projects"; | |||||
import { fetchMonthlyWorkHoursReport, fetchProjectCashFlowReport } from "@/app/api/reports/actions"; | |||||
import { downloadFile } from "@/app/utils/commonUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
import { MonthlyWorkHoursReportFilter } from "@/app/api/reports"; | |||||
import { records } from "@/app/api/staff/actions"; | |||||
import { StaffResult } from "@/app/api/staff"; | |||||
interface Props { | |||||
staffs: StaffResult[] | |||||
} | |||||
type SearchQuery = Partial<Omit<MonthlyWorkHoursReportFilter, "id">>; | |||||
type SearchParamNames = keyof SearchQuery; | |||||
const GenerateMonthlyWorkHoursReport: React.FC<Props> = ({ staffs }) => { | |||||
const { t } = useTranslation(); | |||||
const staffCombo = staffs.map(staff => `${staff.name} - ${staff.staffId}`) | |||||
console.log(staffs) | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
() => [ | |||||
{ label: t("Staff"), | |||||
paramName: "staff", | |||||
type: "select", | |||||
options: staffCombo, | |||||
needAll: false}, | |||||
], | |||||
[t], | |||||
); | |||||
return ( | |||||
<> | |||||
<SearchBox | |||||
criteria={searchCriteria} | |||||
onSearch={async (query: any) => { | |||||
if (query.staff.length > 0 && query.staff.toLocaleLowerCase() !== "all") { | |||||
const index = staffCombo.findIndex(staff => staff === query.staff) | |||||
const response = await fetchMonthlyWorkHoursReport({ id: staffs[index].id, yearMonth: "2023-03" }) | |||||
if (response) { | |||||
downloadFile(new Uint8Array(response.blobValue), response.filename!!) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/> | |||||
</> | |||||
) | |||||
} | |||||
export default GenerateMonthlyWorkHoursReport |
@@ -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 GenerateMonthlyWorkHoursReportLoading: 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 GenerateMonthlyWorkHoursReportLoading; |
@@ -0,0 +1,19 @@ | |||||
import React from "react"; | |||||
import GenerateMonthlyWorkHoursReportLoading from "./GenerateMonthlyWorkHoursReportLoading"; | |||||
import { fetchProjects } from "@/app/api/projects"; | |||||
import GenerateMonthlyWorkHoursReport from "./GenerateMonthlyWorkHoursReport"; | |||||
import { fetchStaff } from "@/app/api/staff"; | |||||
interface SubComponents { | |||||
Loading: typeof GenerateMonthlyWorkHoursReportLoading; | |||||
} | |||||
const GenerateMonthlyWorkHoursReportWrapper: React.FC & SubComponents = async () => { | |||||
const staffs = await fetchStaff(); | |||||
return <GenerateMonthlyWorkHoursReport staffs={staffs}/>; | |||||
}; | |||||
GenerateMonthlyWorkHoursReportWrapper.Loading = GenerateMonthlyWorkHoursReportLoading; | |||||
export default GenerateMonthlyWorkHoursReportWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./GenerateMonthlyWorkHoursReportWrapper"; |
@@ -128,6 +128,7 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
{icon: <Analytics />, label:"Project P&L Report", path: "/analytics/ProjectPLReport"}, | {icon: <Analytics />, label:"Project P&L Report", path: "/analytics/ProjectPLReport"}, | ||||
{icon: <Analytics />, label:"Financial Status Report", path: "/analytics/FinancialStatusReport"}, | {icon: <Analytics />, label:"Financial Status Report", path: "/analytics/FinancialStatusReport"}, | ||||
{icon: <Analytics />, label:"Project Cash Flow Report", path: "/analytics/ProjectCashFlowReport"}, | {icon: <Analytics />, label:"Project Cash Flow Report", path: "/analytics/ProjectCashFlowReport"}, | ||||
{icon: <Analytics />, label:"Staff Monthly Work Hours Analysis Report", path: "/analytics/StaffMonthlyWorkHoursAnalysisReport"}, | |||||
], | ], | ||||
}, | }, | ||||
{ | { | ||||
@@ -142,7 +143,7 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => { | |||||
{ icon: <Position />, label: "Position", path: "/settings/position" }, | { icon: <Position />, label: "Position", path: "/settings/position" }, | ||||
{ icon: <Salary />, label: "Salary", path: "/settings/salary" }, | { icon: <Salary />, label: "Salary", path: "/settings/salary" }, | ||||
{ icon: <Team />, label: "Team", path: "/settings/team" }, | { icon: <Team />, label: "Team", path: "/settings/team" }, | ||||
{ icon: <ManageAccountsIcon />, label: "User", path: "/settings/user" }, | |||||
// { icon: <ManageAccountsIcon />, label: "User", path: "/settings/user" }, | |||||
{ icon: <ManageAccountsIcon />, label: "User Group", path: "/settings/group" }, | { icon: <ManageAccountsIcon />, label: "User Group", path: "/settings/group" }, | ||||
{ icon: <Holiday />, label: "Holiday", path: "/settings/holiday" }, | { icon: <Holiday />, label: "Holiday", path: "/settings/holiday" }, | ||||
], | ], | ||||