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