| @@ -1,9 +1,10 @@ | |||
| "use server"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { serverFetchBlob, serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { Dayjs } from "dayjs"; | |||
| import { cache } from "react"; | |||
| import { FileResponse } from "../reports/actions"; | |||
| 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 | |||
| hoursSpent: number; | |||
| hoursSpentOther: number; | |||
| currentStaffHoursSpent: number; | |||
| currentStaffHoursSpentOther: number; | |||
| hoursAllocated: number; | |||
| } | |||
| @@ -19,9 +19,10 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||
| import { Suspense } from "react"; | |||
| 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 VisibilityIcon from '@mui/icons-material/Visibility'; | |||
| import { downloadFile } from "@/app/utils/commonUtil"; | |||
| const ProjectFinancialSummary: React.FC = () => { | |||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||
| @@ -458,7 +459,11 @@ const columns2 = [ | |||
| 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) | |||
| }; | |||
| @@ -16,9 +16,11 @@ import { Props as UserWorkspaceProps } from "./UserWorkspacePage"; | |||
| interface Props { | |||
| 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"); | |||
| // Projects | |||
| @@ -78,7 +80,7 @@ const AssignedProjects: React.FC<Props> = ({ assignedProjects }) => { | |||
| </Stack> | |||
| </CardContent> | |||
| </Card> | |||
| <ProjectGrid projects={filteredProjects} /> | |||
| <ProjectGrid projects={filteredProjects} maintainNormalStaffWorkspaceAbility={maintainNormalStaffWorkspaceAbility} maintainManagementStaffWorkspaceAbility={maintainManagementStaffWorkspaceAbility}/> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -6,9 +6,11 @@ import { AssignedProject } from "@/app/api/projects"; | |||
| interface Props { | |||
| projects: AssignedProject[]; | |||
| maintainNormalStaffWorkspaceAbility?: boolean; | |||
| maintainManagementStaffWorkspaceAbility?: boolean; | |||
| } | |||
| const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||
| const ProjectGrid: React.FC<Props> = ({ projects, maintainNormalStaffWorkspaceAbility, maintainManagementStaffWorkspaceAbility }) => { | |||
| const { t } = useTranslation("home"); | |||
| return ( | |||
| <Box> | |||
| @@ -30,7 +32,7 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||
| {project.name} | |||
| </Typography> | |||
| {/* Hours Spent */} | |||
| <Typography variant="subtitle2">{t("Hours Spent:")}</Typography> | |||
| {(Boolean(maintainNormalStaffWorkspaceAbility) || Boolean(maintainManagementStaffWorkspaceAbility)) && <><Typography variant="subtitle2">{t("Hours Spent:")}</Typography> | |||
| <Box | |||
| sx={{ | |||
| display: "flex", | |||
| @@ -40,7 +42,7 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||
| > | |||
| <Typography variant="caption">{t("Normal")}</Typography> | |||
| <Typography> | |||
| {manhourFormatter.format(project.hoursSpent)} | |||
| {manhourFormatter.format(Boolean(maintainManagementStaffWorkspaceAbility) ? project.hoursSpent : project.currentStaffHoursSpent)} | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| @@ -52,11 +54,11 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||
| > | |||
| <Typography variant="caption">{t("(Others)")}</Typography> | |||
| <Typography>{`(${manhourFormatter.format( | |||
| project.hoursSpentOther, | |||
| Boolean(maintainManagementStaffWorkspaceAbility) ? project.hoursSpentOther : project.currentStaffHoursSpentOther, | |||
| )})`}</Typography> | |||
| </Box> | |||
| </Box></>} | |||
| {/* Hours Allocated */} | |||
| <Box | |||
| {Boolean(maintainManagementStaffWorkspaceAbility) && <Box | |||
| sx={{ | |||
| display: "flex", | |||
| justifyContent: "space-between", | |||
| @@ -69,7 +71,7 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||
| <Typography> | |||
| {manhourFormatter.format(project.hoursAllocated)} | |||
| </Typography> | |||
| </Box> | |||
| </Box>} | |||
| </CardContent> | |||
| </Card> | |||
| </Grid> | |||
| @@ -35,7 +35,9 @@ export interface Props { | |||
| holidays: HolidaysResult[]; | |||
| teamTimesheets: TeamTimeSheets; | |||
| teamLeaves: TeamLeaves; | |||
| fastEntryEnabled?: boolean; | |||
| fastEntryEnabled: boolean; | |||
| maintainNormalStaffWorkspaceAbility: boolean; | |||
| maintainManagementStaffWorkspaceAbility: boolean; | |||
| } | |||
| const menuItemSx: SxProps = { | |||
| @@ -53,6 +55,8 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||
| teamTimesheets, | |||
| teamLeaves, | |||
| fastEntryEnabled, | |||
| maintainNormalStaffWorkspaceAbility, | |||
| maintainManagementStaffWorkspaceAbility, | |||
| }) => { | |||
| const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | |||
| @@ -190,7 +194,7 @@ const UserWorkspacePage: React.FC<Props> = ({ | |||
| timesheetRecords={defaultTimesheets} | |||
| /> | |||
| {assignedProjects.length > 0 ? ( | |||
| <AssignedProjects assignedProjects={assignedProjects} /> | |||
| <AssignedProjects assignedProjects={assignedProjects} maintainNormalStaffWorkspaceAbility={maintainNormalStaffWorkspaceAbility} maintainManagementStaffWorkspaceAbility={maintainManagementStaffWorkspaceAbility}/> | |||
| ) : ( | |||
| <Typography variant="subtitle1"> | |||
| {t("You have no assigned projects!")} | |||
| @@ -12,7 +12,7 @@ import { | |||
| } from "@/app/api/timesheets"; | |||
| import { fetchHolidays } from "@/app/api/holidays"; | |||
| 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 [ | |||
| @@ -38,6 +38,8 @@ const UserWorkspaceWrapper: React.FC = async () => { | |||
| ]); | |||
| 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 ( | |||
| <UserWorkspacePage | |||
| @@ -51,6 +53,8 @@ const UserWorkspaceWrapper: React.FC = async () => { | |||
| holidays={holidays} | |||
| // Change to access check | |||
| fastEntryEnabled={fastEntryEnabled} | |||
| maintainNormalStaffWorkspaceAbility={maintainNormalStaffWorkspaceAbility} | |||
| maintainManagementStaffWorkspaceAbility={maintainManagementStaffWorkspaceAbility} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -58,6 +58,8 @@ export const [ | |||
| DELETE_PROJECT, | |||
| MAINTAIN_TIMESHEET_FAST_TIME_ENTRY, | |||
| VIEW_PROJECT_RESOURCE_CONSUMPTION_RANKING, | |||
| MAINTAIN_NORMAL_STAFF_WORKSPACE, | |||
| MAINTAIN_MANAGEMENT_STAFF_WORKSPACE, | |||
| ] = [ | |||
| 'MAINTAIN_USER', | |||
| 'MAINTAIN_TIMESHEET', | |||
| @@ -96,7 +98,9 @@ export const [ | |||
| 'MAINTAIN_PROJECT', | |||
| 'DELETE_PROJECT', | |||
| '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 = [ | |||