(cherry picked from commit 13b6cc9f12
)
tags/Baseline_30082024_FRONTEND_UAT
@@ -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')) { | ||||