From 6a4bf4d672230132fc7c099261daf8897f85f992 Mon Sep 17 00:00:00 2001 From: chamikaJ Date: Fri, 30 May 2025 16:26:16 +0530 Subject: [PATCH] feat(finance-permissions): implement permission checks for finance data editing - Added permission checks for editing finance data, including fixed costs and rate cards. - Introduced utility functions to determine user permissions based on roles (admin, project manager). - Updated finance and rate card components to conditionally render UI elements based on user permissions. - Displayed alerts for users with limited access to inform them of their editing capabilities. --- .../finance-table/finance-table.tsx | 23 +++++-- .../finance/project-view-finance.tsx | 27 +++++++- .../reatecard-table/ratecard-table.tsx | 65 ++++++++++++------- .../src/utils/finance-permissions.ts | 58 +++++++++++++++++ 4 files changed, 145 insertions(+), 28 deletions(-) create mode 100644 worklenz-frontend/src/utils/finance-permissions.ts diff --git a/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx b/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx index 1cbfe8a3..d7540c58 100644 --- a/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx @@ -22,6 +22,8 @@ import { useAppDispatch } from '@/hooks/useAppDispatch'; import { setSelectedTaskId, setShowTaskDrawer, fetchTask } from '@/features/task-drawer/task-drawer.slice'; import { useParams } from 'react-router-dom'; import { parseTimeToSeconds } from '@/utils/timeUtils'; +import { useAuthService } from '@/hooks/useAuth'; +import { canEditFixedCost } from '@/utils/finance-permissions'; import './finance-table.css'; type FinanceTableProps = { @@ -48,6 +50,12 @@ const FinanceTable = ({ const taskGroups = useAppSelector((state) => state.projectFinances.taskGroups); const activeGroup = useAppSelector((state) => state.projectFinances.activeGroup); + // Auth and permissions + const auth = useAuthService(); + const currentSession = auth.getCurrentSession(); + const { project } = useAppSelector((state) => state.projectReducer); + const hasEditPermission = canEditFixedCost(currentSession, project); + // Update local state when table.tasks or Redux store changes useEffect(() => { const updatedGroup = taskGroups.find(g => g.group_id === table.group_id); @@ -110,6 +118,8 @@ const FinanceTable = ({ return {formattedTotals.total_time_logged}; case FinanceTableColumnKeys.ESTIMATED_COST: return {formatNumber(formattedTotals.estimated_cost)}; + case FinanceTableColumnKeys.COST: + return {formatNumber(formattedTotals.estimated_cost)}; case FinanceTableColumnKeys.FIXED_COST: return {formatNumber(formattedTotals.fixed_cost)}; case FinanceTableColumnKeys.TOTAL_BUDGET: @@ -269,7 +279,7 @@ const FinanceTable = ({ case FinanceTableColumnKeys.ESTIMATED_COST: return {formatNumber(task.estimated_cost)}; case FinanceTableColumnKeys.FIXED_COST: - return selectedTask?.id === task.id ? ( + return selectedTask?.id === task.id && hasEditPermission ? ( { @@ -295,12 +305,17 @@ const FinanceTable = ({ /> ) : ( { + style={{ + cursor: hasEditPermission ? 'pointer' : 'default', + width: '100%', + display: 'block', + opacity: hasEditPermission ? 1 : 0.7 + }} + onClick={hasEditPermission ? (e) => { e.stopPropagation(); setSelectedTask(task); setEditingFixedCostValue(task.fixed_cost); - }} + } : undefined} > {formatNumber(task.fixed_cost)} diff --git a/worklenz-frontend/src/pages/projects/projectView/finance/project-view-finance.tsx b/worklenz-frontend/src/pages/projects/projectView/finance/project-view-finance.tsx index ad296f7d..2872d1ac 100644 --- a/worklenz-frontend/src/pages/projects/projectView/finance/project-view-finance.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/finance/project-view-finance.tsx @@ -1,4 +1,4 @@ -import { Button, ConfigProvider, Flex, Select, Typography, message } from 'antd'; +import { Button, ConfigProvider, Flex, Select, Typography, message, Alert } from 'antd'; import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -12,6 +12,8 @@ import { RootState } from '@/app/store'; import FinanceTableWrapper from './finance-tab/finance-table/finance-table-wrapper'; import RatecardTable from './ratecard-tab/reatecard-table/ratecard-table'; import ImportRatecardsDrawer from '@/features/finance/ratecard-drawer/import-ratecards-drawer'; +import { useAuthService } from '@/hooks/useAuth'; +import { hasFinanceEditPermission } from '@/utils/finance-permissions'; const ProjectViewFinance = () => { const { projectId } = useParams<{ projectId: string }>(); @@ -23,6 +25,11 @@ const ProjectViewFinance = () => { const { refreshTimestamp, project } = useAppSelector((state: RootState) => state.projectReducer); const phaseList = useAppSelector((state) => state.phaseReducer.phaseList); + // Auth and permissions + const auth = useAuthService(); + const currentSession = auth.getCurrentSession(); + const hasEditPermission = hasFinanceEditPermission(currentSession, project); + useEffect(() => { if (projectId) { dispatch(fetchProjectFinances({ projectId, groupBy: activeGroup })); @@ -146,10 +153,28 @@ const ProjectViewFinance = () => { {/* Tab Content */} {activeTab === 'finance' ? (
+ {!hasEditPermission && ( + + )}
) : ( + {!hasEditPermission && ( + + )} { @@ -31,6 +33,14 @@ const RatecardTable: React.FC = () => { const isLoading = useAppSelector((state) => state.projectFinanceRateCard.isLoading); const currency = useAppSelector((state) => state.financeReducer.currency).toUpperCase(); const rateInputRefs = React.useRef>([]); + + // Auth and permissions + const auth = useAuthService(); + const currentSession = auth.getCurrentSession(); + const { project } = useAppSelector((state) => state.projectReducer); + const hasEditPermission = canEditRateCard(currentSession, project); + const canAddMembers = canAddMembersToRateCard(currentSession, project); + // Local state for editing const [roles, setRoles] = useState(rolesRedux); const [addingRow, setAddingRow] = useState(false); @@ -238,6 +248,7 @@ const RatecardTable: React.FC = () => { type="number" value={roles[index]?.rate ?? 0} min={0} + disabled={!hasEditPermission} style={{ background: 'transparent', border: 'none', @@ -245,10 +256,12 @@ const RatecardTable: React.FC = () => { padding: 0, width: 80, textAlign: 'right', + opacity: hasEditPermission ? 1 : 0.7, + cursor: hasEditPermission ? 'text' : 'not-allowed' }} - onChange={(e) => handleRateChange(e.target.value, index)} - onBlur={(e) => handleRateBlur(e.target.value, index)} - onPressEnter={(e) => handleRateBlur(e.target.value, index)} + onChange={hasEditPermission ? (e) => handleRateChange(e.target.value, index) : undefined} + onBlur={hasEditPermission ? (e) => handleRateBlur(e.target.value, index) : undefined} + onPressEnter={hasEditPermission ? (e) => handleRateBlur(e.target.value, index) : undefined} /> ), }, @@ -265,15 +278,17 @@ const RatecardTable: React.FC = () => { ) : null; })} -
- handleMemberChange(memberId, index, record)} - memberlist={members} - assignedMembers={assignedMembers} // Pass assigned members here - /> -
+ {canAddMembers && ( +
+ handleMemberChange(memberId, index, record)} + memberlist={members} + assignedMembers={assignedMembers} // Pass assigned members here + /> +
+ )} ), }, @@ -282,14 +297,16 @@ const RatecardTable: React.FC = () => { key: 'actions', align: 'center', render: (_: any, record: JobRoleType, index: number) => ( - handleDelete(record, index)} - okText={t('yes')} - cancelText={t('no')} - > - + {hasEditPermission && ( + + )} {/*