diff --git a/src/app/api/reports/index.ts b/src/app/api/reports/index.ts index 934f02c..7db4809 100644 --- a/src/app/api/reports/index.ts +++ b/src/app/api/reports/index.ts @@ -120,8 +120,10 @@ export interface CostAndExpenseReportRequest { // - Cross Team Charge Report export interface CrossTeamChargeReportFilter { month: string; + team: string[]; } export interface CrossTeamChargeReportRequest { month: string; + teamId: number | "All"; } \ No newline at end of file diff --git a/src/components/GenerateCrossTeamChargeReport/GenerateCrossTeamChargeReport.tsx b/src/components/GenerateCrossTeamChargeReport/GenerateCrossTeamChargeReport.tsx index 2d7aaa4..683b701 100644 --- a/src/components/GenerateCrossTeamChargeReport/GenerateCrossTeamChargeReport.tsx +++ b/src/components/GenerateCrossTeamChargeReport/GenerateCrossTeamChargeReport.tsx @@ -6,15 +6,20 @@ import { useTranslation } from "react-i18next"; import { CrossTeamChargeReportFilter } from "@/app/api/reports"; import { fetchCrossTeamChargeReport } from "@/app/api/reports/actions"; import { downloadFile } from "@/app/utils/commonUtil"; +import { TeamResult } from "@/app/api/team"; +import { SessionStaff } from "@/config/authConfig"; interface Props { + teams: TeamResult[]; + userStaff: SessionStaff; } type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; -const GenerateCrossTeamChargeReport: React.FC = () => { +const GenerateCrossTeamChargeReport: React.FC = ({ teams, userStaff }) => { const { t } = useTranslation("report"); + const teamCombo = teams.map(team => `${team.code} - ${team.name}`) const searchCriteria: Criterion[] = useMemo( () => [ @@ -23,6 +28,13 @@ const GenerateCrossTeamChargeReport: React.FC = () => { paramName: "month", type: "monthYear", }, + { + label: t("Team"), + paramName: "team", + type: "select", + options: teamCombo, + needAll: !Boolean(userStaff?.isTeamLead) + }, ], [t], ); @@ -33,10 +45,12 @@ const GenerateCrossTeamChargeReport: React.FC = () => { criteria={searchCriteria} onSearch={async (query) => { - console.log(query.month) - if (Boolean(query.month)) { + console.log(query) + if (Boolean(query.month) && Boolean(query.team)) { // const projectIndex = projectCombo.findIndex(({value}) => value === parseInt(query.project)) - const response = await fetchCrossTeamChargeReport({ month: query.month }) + const teamIndex = teamCombo.findIndex(team => team === query.team) + + const response = await fetchCrossTeamChargeReport({ month: query.month, teamId: teamIndex >= 0 ? teams[teamIndex].id : "All", }) if (response) { downloadFile(new Uint8Array(response.blobValue), response.filename!!) } diff --git a/src/components/GenerateCrossTeamChargeReport/GenerateCrossTeamChargeReportWrapper.tsx b/src/components/GenerateCrossTeamChargeReport/GenerateCrossTeamChargeReportWrapper.tsx index 5e7d2e5..a9aaea2 100644 --- a/src/components/GenerateCrossTeamChargeReport/GenerateCrossTeamChargeReportWrapper.tsx +++ b/src/components/GenerateCrossTeamChargeReport/GenerateCrossTeamChargeReportWrapper.tsx @@ -1,13 +1,18 @@ import React from "react"; import GenerateCrossTeamChargeReportLoading from "./GenerateCrossTeamChargeReportLoading"; import GenerateCrossTeamChargeReport from "./GenerateCrossTeamChargeReport"; +import { fetchTeam } from "@/app/api/team"; +import { getUserStaff } from "@/app/utils/commonUtil"; interface SubComponents { Loading: typeof GenerateCrossTeamChargeReportLoading; } const GenerateCrossTeamChargeReportWrapper: React.FC & SubComponents = async () => { - return ; + + const [teams, userStaff] = await Promise.all([fetchTeam(), getUserStaff()]) + + return team.id === userStaff?.teamId)} userStaff={userStaff}/>; }; GenerateCrossTeamChargeReportWrapper.Loading = GenerateCrossTeamChargeReportLoading; diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index bab1493..83a5535 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -36,7 +36,6 @@ import ManageAccountsIcon from "@mui/icons-material/ManageAccounts"; import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; import FileUploadIcon from '@mui/icons-material/FileUpload'; import { - GENERATE_REPORTS, IMPORT_INVOICE, IMPORT_RECEIPT, MAINTAIN_PROJECT, @@ -68,6 +67,16 @@ import { MAINTAIN_GROUP, MAINTAIN_HOLIDAY, VIEW_PROJECT_RESOURCE_CONSUMPTION_RANKING, + GENERATE_LATE_START_REPORTS, + GENERATE_PROJECT_POTENTIAL_DELAY_REPORT, + GENERATE_RESOURCE_OVERCONSUMPTION_REPORT, + GENERATE_COST_ANT_EXPENSE_REPORT, + GENERATE_PROJECT_COMPLETION_REPORT, + GENERATE_PROJECT_PANDL_REPORT, + GENERATE_FINANCIAL_STATUS_REPORT, + GENERATE_PROJECT_CASH_FLOW_REPORT, + GENERATE_STAFF_MONTHLY_WORK_HOURS_ANALYSIS_REPORT, + GENERATE_CROSS_TEAM_CHARGE_REPORT, } from "@/middleware"; import { SessionWithAbilities } from "../AppBar/NavigationToggle"; import { authOptions } from "@/config/authConfig"; @@ -180,7 +189,18 @@ const NavigationContent: React.FC = ({ abilities, username }) => { icon: , label: "Analysis Report", path: "", - isHidden: ![GENERATE_REPORTS].some((ability) => + isHidden: ![ + GENERATE_LATE_START_REPORTS, + GENERATE_PROJECT_POTENTIAL_DELAY_REPORT, + GENERATE_RESOURCE_OVERCONSUMPTION_REPORT, + GENERATE_COST_ANT_EXPENSE_REPORT, + GENERATE_PROJECT_COMPLETION_REPORT, + GENERATE_PROJECT_PANDL_REPORT, + GENERATE_FINANCIAL_STATUS_REPORT, + GENERATE_PROJECT_CASH_FLOW_REPORT, + GENERATE_STAFF_MONTHLY_WORK_HOURS_ANALYSIS_REPORT, + GENERATE_CROSS_TEAM_CHARGE_REPORT, + ].some((ability) => abilities!.includes(ability), ), children: [ @@ -188,26 +208,41 @@ const NavigationContent: React.FC = ({ abilities, username }) => { icon: , label: "Late Start Report", path: "/analytics/LateStartReport", + isHidden: ![GENERATE_LATE_START_REPORTS].some((ability) => + abilities!.includes(ability), + ), }, { icon: , label: "Project Potential Delay Report", path: "/analytics/ProjectPotentialDelayReport", + isHidden: ![GENERATE_PROJECT_POTENTIAL_DELAY_REPORT].some((ability) => + abilities!.includes(ability), + ), }, { icon: , label: "Resource Overconsumption Report", path: "/analytics/ResourceOverconsumptionReport", + isHidden: ![GENERATE_RESOURCE_OVERCONSUMPTION_REPORT].some((ability) => + abilities!.includes(ability), + ), }, { icon: , label: "Cost and Expense Report", path: "/analytics/CostandExpenseReport", + isHidden: ![GENERATE_COST_ANT_EXPENSE_REPORT].some((ability) => + abilities!.includes(ability), + ), }, { icon: , label: "Project Completion Report", path: "/analytics/ProjectCompletionReport", + isHidden: ![GENERATE_PROJECT_COMPLETION_REPORT].some((ability) => + abilities!.includes(ability), + ), }, // { // icon: , @@ -223,26 +258,41 @@ const NavigationContent: React.FC = ({ abilities, username }) => { icon: , label: "Project P&L Report", path: "/analytics/ProjectPandLReport", + isHidden: ![GENERATE_PROJECT_COMPLETION_REPORT].some((ability) => + abilities!.includes(ability), + ), }, { icon: , label: "Financial Status Report", path: "/analytics/FinancialStatusReport", + isHidden: ![GENERATE_FINANCIAL_STATUS_REPORT].some((ability) => + abilities!.includes(ability), + ), }, { icon: , label: "Project Cash Flow Report", path: "/analytics/ProjectCashFlowReport", + isHidden: ![GENERATE_PROJECT_CASH_FLOW_REPORT].some((ability) => + abilities!.includes(ability), + ), }, { icon: , label: "Staff Monthly Work Hours Analysis Report", path: "/analytics/StaffMonthlyWorkHoursAnalysisReport", + isHidden: ![GENERATE_STAFF_MONTHLY_WORK_HOURS_ANALYSIS_REPORT].some((ability) => + abilities!.includes(ability), + ), }, { icon: , label: "Cross Team Charge Report", path: "/analytics/CrossTeamChargeReport", + isHidden: ![GENERATE_CROSS_TEAM_CHARGE_REPORT].some((ability) => + abilities!.includes(ability), + ), }, ], }, diff --git a/src/middleware.ts b/src/middleware.ts index cb48c5b..0448add 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -48,7 +48,6 @@ export const [ VIEW_DASHBOARD_SELF, VIEW_DASHBOARD_ALL, IMPORT_INVOICE, - GENERATE_REPORTS, VIEW_STAFF_PROFILE, IMPORT_RECEIPT, MAINTAIN_TASK_TEMPLATE, @@ -60,6 +59,16 @@ export const [ VIEW_PROJECT_RESOURCE_CONSUMPTION_RANKING, MAINTAIN_NORMAL_STAFF_WORKSPACE, MAINTAIN_MANAGEMENT_STAFF_WORKSPACE, + GENERATE_LATE_START_REPORTS, + GENERATE_PROJECT_POTENTIAL_DELAY_REPORT, + GENERATE_RESOURCE_OVERCONSUMPTION_REPORT, + GENERATE_COST_ANT_EXPENSE_REPORT, + GENERATE_PROJECT_COMPLETION_REPORT, + GENERATE_PROJECT_PANDL_REPORT, + GENERATE_FINANCIAL_STATUS_REPORT, + GENERATE_PROJECT_CASH_FLOW_REPORT, + GENERATE_STAFF_MONTHLY_WORK_HOURS_ANALYSIS_REPORT, + GENERATE_CROSS_TEAM_CHARGE_REPORT, ] = [ 'MAINTAIN_USER', 'MAINTAIN_TIMESHEET', @@ -89,7 +98,6 @@ export const [ 'VIEW_DASHBOARD_SELF', 'VIEW_DASHBOARD_ALL', 'IMPORT_INVOICE', - 'GENERATE_REPORTS', 'VIEW_STAFF_PROFILE', 'IMPORT_RECEIPT', 'MAINTAIN_TASK_TEMPLATE', @@ -100,7 +108,17 @@ export const [ 'MAINTAIN_TIMESHEET_FAST_TIME_ENTRY', 'VIEW_PROJECT_RESOURCE_CONSUMPTION_RANKING', 'MAINTAIN_NORMAL_STAFF_WORKSPACE', - 'MAINTAIN_MANAGEMENT_STAFF_WORKSPACE' + 'MAINTAIN_MANAGEMENT_STAFF_WORKSPACE', + 'GENERATE_LATE_START_REPORTS', + 'GENERATE_PROJECT_POTENTIAL_DELAY_REPORT', + 'GENERATE_RESOURCE_OVERCONSUMPTION_REPORT', + 'GENERATE_COST_ANT_EXPENSE_REPORT', + 'GENERATE_PROJECT_COMPLETION_REPORT', + 'GENERATE_PROJECT_P&L_REPORT', + 'GENERATE_FINANCIAL_STATUS_REPORT', + 'GENERATE_PROJECT_CASH_FLOW_REPORT', + 'GENERATE_STAFF_MONTHLY_WORK_HOURS_ANALYSIS_REPORT', + 'GENERATE_CROSS_TEAM_CHARGE_REPORT', ] const PRIVATE_ROUTES = [ @@ -224,7 +242,57 @@ export default async function middleware( } if (req.nextUrl.pathname.startsWith('/analytics')) { - isAuth = [GENERATE_REPORTS].some((ability) => abilities.includes(ability)); + isAuth = [ + GENERATE_LATE_START_REPORTS, + GENERATE_PROJECT_POTENTIAL_DELAY_REPORT, + GENERATE_RESOURCE_OVERCONSUMPTION_REPORT, + GENERATE_COST_ANT_EXPENSE_REPORT, + GENERATE_PROJECT_COMPLETION_REPORT, + GENERATE_PROJECT_PANDL_REPORT, + GENERATE_FINANCIAL_STATUS_REPORT, + GENERATE_PROJECT_CASH_FLOW_REPORT, + GENERATE_STAFF_MONTHLY_WORK_HOURS_ANALYSIS_REPORT, + GENERATE_CROSS_TEAM_CHARGE_REPORT,].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/LateStartReport')) { + isAuth = [GENERATE_LATE_START_REPORTS].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/ProjectPotentialDelayReport')) { + isAuth = [GENERATE_PROJECT_POTENTIAL_DELAY_REPORT].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/ResourceOverconsumptionReport')) { + isAuth = [GENERATE_RESOURCE_OVERCONSUMPTION_REPORT].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/CostandExpenseReport')) { + isAuth = [GENERATE_COST_ANT_EXPENSE_REPORT].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/ProjectCompletionReport')) { + isAuth = [GENERATE_PROJECT_COMPLETION_REPORT].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/ProjectPandLReport')) { + isAuth = [GENERATE_PROJECT_PANDL_REPORT].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/FinancialStatusReport')) { + isAuth = [GENERATE_FINANCIAL_STATUS_REPORT].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/ProjectCashFlowReport')) { + isAuth = [GENERATE_PROJECT_CASH_FLOW_REPORT].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/StaffMonthlyWorkHoursAnalysisReport')) { + isAuth = [GENERATE_STAFF_MONTHLY_WORK_HOURS_ANALYSIS_REPORT].some((ability) => abilities.includes(ability)); + } + + if (req.nextUrl.pathname.startsWith('/analytics/CrossTeamChargeReport')) { + isAuth = [GENERATE_CROSS_TEAM_CHARGE_REPORT].some((ability) => abilities.includes(ability)); } if (req.nextUrl.pathname.startsWith('/settings/staff/edit')) {