| @@ -1,9 +1,10 @@ | |||||
| "use server"; | "use server"; | ||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| import { serverFetchBlob, serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { Dayjs } from "dayjs"; | import { Dayjs } from "dayjs"; | ||||
| import { cache } from "react"; | import { cache } from "react"; | ||||
| import { FileResponse } from "../reports/actions"; | |||||
| export interface FinancialSummaryByClientResult { | export interface FinancialSummaryByClientResult { | ||||
| @@ -65,3 +66,30 @@ export const searchFinancialSummaryByProject = cache(async (teamId?: number, cus | |||||
| } | } | ||||
| }); | }); | ||||
| export interface FinancialSummaryByClientExcel { | |||||
| customerCode: string; | |||||
| customerName: string; | |||||
| projectNo: number; | |||||
| totalFee: number; | |||||
| cumulativeExpenditure: number; | |||||
| totalInvoiced: number; | |||||
| totalReceived: number; | |||||
| } | |||||
| export interface ExportFinancialSummaryByClientExcel { | |||||
| financialSummaryByClients: FinancialSummaryByClientExcel[] | |||||
| } | |||||
| export const exportFinancialSummaryByClientExcel = cache(async (data: ExportFinancialSummaryByClientExcel) => { | |||||
| const reportBlob = await serverFetchBlob<FileResponse>( | |||||
| `${BASE_API_URL}/dashboard/exportFinancialSummaryByClientExcel`, | |||||
| { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| return reportBlob | |||||
| }) | |||||
| @@ -92,6 +92,8 @@ export interface AssignedProject extends ProjectWithTasks { | |||||
| // Manhour info | // Manhour info | ||||
| hoursSpent: number; | hoursSpent: number; | ||||
| hoursSpentOther: number; | hoursSpentOther: number; | ||||
| currentStaffHoursSpent: number; | |||||
| currentStaffHoursSpentOther: number; | |||||
| hoursAllocated: number; | hoursAllocated: number; | ||||
| } | } | ||||
| @@ -19,9 +19,10 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | ||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| import { fetchFinancialSummaryCard } from "@/app/api/financialsummary"; | import { fetchFinancialSummaryCard } from "@/app/api/financialsummary"; | ||||
| import { searchFinancialSummaryByClient,searchFinancialSummaryByProject } from "@/app/api/financialsummary/actions"; | |||||
| import { exportFinancialSummaryByClientExcel, searchFinancialSummaryByClient,searchFinancialSummaryByProject } from "@/app/api/financialsummary/actions"; | |||||
| import ProjectFinancialCard from "./ProjectFinancialCard"; | import ProjectFinancialCard from "./ProjectFinancialCard"; | ||||
| import VisibilityIcon from '@mui/icons-material/Visibility'; | import VisibilityIcon from '@mui/icons-material/Visibility'; | ||||
| import { downloadFile } from "@/app/utils/commonUtil"; | |||||
| const ProjectFinancialSummary: React.FC = () => { | const ProjectFinancialSummary: React.FC = () => { | ||||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | const [SearchCriteria, setSearchCriteria] = React.useState({}); | ||||
| @@ -458,7 +459,11 @@ const columns2 = [ | |||||
| fetchProjectTableData(params.row.teamId,params.row.cid) | fetchProjectTableData(params.row.teamId,params.row.cid) | ||||
| }; | }; | ||||
| const handleExportByClient = () => { | |||||
| const handleExportByClient = async () => { | |||||
| const response = await exportFinancialSummaryByClientExcel({financialSummaryByClients: clientFinancialRows}) | |||||
| if (response) { | |||||
| downloadFile(new Uint8Array(response.blobValue), response.filename!!) | |||||
| } | |||||
| console.log(clientFinancialRows) | console.log(clientFinancialRows) | ||||
| }; | }; | ||||
| @@ -16,9 +16,11 @@ import { Props as UserWorkspaceProps } from "./UserWorkspacePage"; | |||||
| interface Props { | interface Props { | ||||
| assignedProjects: UserWorkspaceProps["assignedProjects"]; | assignedProjects: UserWorkspaceProps["assignedProjects"]; | ||||
| maintainNormalStaffWorkspaceAbility?: boolean; | |||||
| maintainManagementStaffWorkspaceAbility?: boolean; | |||||
| } | } | ||||
| const AssignedProjects: React.FC<Props> = ({ assignedProjects }) => { | |||||
| const AssignedProjects: React.FC<Props> = ({ assignedProjects, maintainNormalStaffWorkspaceAbility, maintainManagementStaffWorkspaceAbility }) => { | |||||
| const { t } = useTranslation("home"); | const { t } = useTranslation("home"); | ||||
| // Projects | // Projects | ||||
| @@ -78,7 +80,7 @@ const AssignedProjects: React.FC<Props> = ({ assignedProjects }) => { | |||||
| </Stack> | </Stack> | ||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| <ProjectGrid projects={filteredProjects} /> | |||||
| <ProjectGrid projects={filteredProjects} maintainNormalStaffWorkspaceAbility={maintainNormalStaffWorkspaceAbility} maintainManagementStaffWorkspaceAbility={maintainManagementStaffWorkspaceAbility}/> | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -6,9 +6,11 @@ import { AssignedProject } from "@/app/api/projects"; | |||||
| interface Props { | interface Props { | ||||
| projects: AssignedProject[]; | projects: AssignedProject[]; | ||||
| maintainNormalStaffWorkspaceAbility?: boolean; | |||||
| maintainManagementStaffWorkspaceAbility?: boolean; | |||||
| } | } | ||||
| const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||||
| const ProjectGrid: React.FC<Props> = ({ projects, maintainNormalStaffWorkspaceAbility, maintainManagementStaffWorkspaceAbility }) => { | |||||
| const { t } = useTranslation("home"); | const { t } = useTranslation("home"); | ||||
| return ( | return ( | ||||
| <Box> | <Box> | ||||
| @@ -30,7 +32,7 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||||
| {project.name} | {project.name} | ||||
| </Typography> | </Typography> | ||||
| {/* Hours Spent */} | {/* Hours Spent */} | ||||
| <Typography variant="subtitle2">{t("Hours Spent:")}</Typography> | |||||
| {(Boolean(maintainNormalStaffWorkspaceAbility) || Boolean(maintainManagementStaffWorkspaceAbility)) && <><Typography variant="subtitle2">{t("Hours Spent:")}</Typography> | |||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| display: "flex", | display: "flex", | ||||
| @@ -40,7 +42,7 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||||
| > | > | ||||
| <Typography variant="caption">{t("Normal")}</Typography> | <Typography variant="caption">{t("Normal")}</Typography> | ||||
| <Typography> | <Typography> | ||||
| {manhourFormatter.format(project.hoursSpent)} | |||||
| {manhourFormatter.format(Boolean(maintainManagementStaffWorkspaceAbility) ? project.hoursSpent : project.currentStaffHoursSpent)} | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| <Box | <Box | ||||
| @@ -52,11 +54,11 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||||
| > | > | ||||
| <Typography variant="caption">{t("(Others)")}</Typography> | <Typography variant="caption">{t("(Others)")}</Typography> | ||||
| <Typography>{`(${manhourFormatter.format( | <Typography>{`(${manhourFormatter.format( | ||||
| project.hoursSpentOther, | |||||
| Boolean(maintainManagementStaffWorkspaceAbility) ? project.hoursSpentOther : project.currentStaffHoursSpentOther, | |||||
| )})`}</Typography> | )})`}</Typography> | ||||
| </Box> | |||||
| </Box></>} | |||||
| {/* Hours Allocated */} | {/* Hours Allocated */} | ||||
| <Box | |||||
| {Boolean(maintainManagementStaffWorkspaceAbility) && <Box | |||||
| sx={{ | sx={{ | ||||
| display: "flex", | display: "flex", | ||||
| justifyContent: "space-between", | justifyContent: "space-between", | ||||
| @@ -69,7 +71,7 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||||
| <Typography> | <Typography> | ||||
| {manhourFormatter.format(project.hoursAllocated)} | {manhourFormatter.format(project.hoursAllocated)} | ||||
| </Typography> | </Typography> | ||||
| </Box> | |||||
| </Box>} | |||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| </Grid> | </Grid> | ||||
| @@ -35,7 +35,9 @@ export interface Props { | |||||
| holidays: HolidaysResult[]; | holidays: HolidaysResult[]; | ||||
| teamTimesheets: TeamTimeSheets; | teamTimesheets: TeamTimeSheets; | ||||
| teamLeaves: TeamLeaves; | teamLeaves: TeamLeaves; | ||||
| fastEntryEnabled?: boolean; | |||||
| fastEntryEnabled: boolean; | |||||
| maintainNormalStaffWorkspaceAbility: boolean; | |||||
| maintainManagementStaffWorkspaceAbility: boolean; | |||||
| } | } | ||||
| const menuItemSx: SxProps = { | const menuItemSx: SxProps = { | ||||
| @@ -53,6 +55,8 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
| teamTimesheets, | teamTimesheets, | ||||
| teamLeaves, | teamLeaves, | ||||
| fastEntryEnabled, | fastEntryEnabled, | ||||
| maintainNormalStaffWorkspaceAbility, | |||||
| maintainManagementStaffWorkspaceAbility, | |||||
| }) => { | }) => { | ||||
| const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | ||||
| @@ -190,7 +194,7 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||||
| timesheetRecords={defaultTimesheets} | timesheetRecords={defaultTimesheets} | ||||
| /> | /> | ||||
| {assignedProjects.length > 0 ? ( | {assignedProjects.length > 0 ? ( | ||||
| <AssignedProjects assignedProjects={assignedProjects} /> | |||||
| <AssignedProjects assignedProjects={assignedProjects} maintainNormalStaffWorkspaceAbility={maintainNormalStaffWorkspaceAbility} maintainManagementStaffWorkspaceAbility={maintainManagementStaffWorkspaceAbility}/> | |||||
| ) : ( | ) : ( | ||||
| <Typography variant="subtitle1"> | <Typography variant="subtitle1"> | ||||
| {t("You have no assigned projects!")} | {t("You have no assigned projects!")} | ||||
| @@ -12,7 +12,7 @@ import { | |||||
| } from "@/app/api/timesheets"; | } from "@/app/api/timesheets"; | ||||
| import { fetchHolidays } from "@/app/api/holidays"; | import { fetchHolidays } from "@/app/api/holidays"; | ||||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | import { getUserAbilities } from "@/app/utils/commonUtil"; | ||||
| import { MAINTAIN_TIMESHEET_FAST_TIME_ENTRY } from "@/middleware"; | |||||
| import { MAINTAIN_TIMESHEET_FAST_TIME_ENTRY, MAINTAIN_NORMAL_STAFF_WORKSPACE, MAINTAIN_MANAGEMENT_STAFF_WORKSPACE } from "@/middleware"; | |||||
| const UserWorkspaceWrapper: React.FC = async () => { | const UserWorkspaceWrapper: React.FC = async () => { | ||||
| const [ | const [ | ||||
| @@ -38,6 +38,8 @@ const UserWorkspaceWrapper: React.FC = async () => { | |||||
| ]); | ]); | ||||
| const fastEntryEnabled = abilities.includes(MAINTAIN_TIMESHEET_FAST_TIME_ENTRY) | const fastEntryEnabled = abilities.includes(MAINTAIN_TIMESHEET_FAST_TIME_ENTRY) | ||||
| const maintainNormalStaffWorkspaceAbility = abilities.includes(MAINTAIN_NORMAL_STAFF_WORKSPACE) | |||||
| const maintainManagementStaffWorkspaceAbility = abilities.includes(MAINTAIN_MANAGEMENT_STAFF_WORKSPACE) | |||||
| return ( | return ( | ||||
| <UserWorkspacePage | <UserWorkspacePage | ||||
| @@ -51,6 +53,8 @@ const UserWorkspaceWrapper: React.FC = async () => { | |||||
| holidays={holidays} | holidays={holidays} | ||||
| // Change to access check | // Change to access check | ||||
| fastEntryEnabled={fastEntryEnabled} | fastEntryEnabled={fastEntryEnabled} | ||||
| maintainNormalStaffWorkspaceAbility={maintainNormalStaffWorkspaceAbility} | |||||
| maintainManagementStaffWorkspaceAbility={maintainManagementStaffWorkspaceAbility} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -58,6 +58,8 @@ export const [ | |||||
| DELETE_PROJECT, | DELETE_PROJECT, | ||||
| 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_MANAGEMENT_STAFF_WORKSPACE, | |||||
| ] = [ | ] = [ | ||||
| 'MAINTAIN_USER', | 'MAINTAIN_USER', | ||||
| 'MAINTAIN_TIMESHEET', | 'MAINTAIN_TIMESHEET', | ||||
| @@ -96,7 +98,9 @@ export const [ | |||||
| 'MAINTAIN_PROJECT', | 'MAINTAIN_PROJECT', | ||||
| 'DELETE_PROJECT', | 'DELETE_PROJECT', | ||||
| '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_MANAGEMENT_STAFF_WORKSPACE' | |||||
| ] | ] | ||||
| const PRIVATE_ROUTES = [ | const PRIVATE_ROUTES = [ | ||||