From d39bddc22fc93930b28dbd49eed11af9c01ebcae Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Mon, 28 Jul 2025 07:36:25 +0530 Subject: [PATCH 1/2] feat(project-view): add user permission-based tab filtering - Implemented a new function to filter project view tabs based on user finance permissions, enhancing the user experience by conditionally displaying the finance tab. - Updated the project view component to utilize the new filtering function, ensuring that only authorized users can access finance-related features. - Refactored URL parameter handling to accommodate the filtered tab items, improving state management and user navigation. --- .../src/lib/project/project-view-constants.ts | 21 +++++++++++++++ .../projects/projectView/project-view.tsx | 27 ++++++++++++------- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/worklenz-frontend/src/lib/project/project-view-constants.ts b/worklenz-frontend/src/lib/project/project-view-constants.ts index d97bcdc0..d0f91c0a 100644 --- a/worklenz-frontend/src/lib/project/project-view-constants.ts +++ b/worklenz-frontend/src/lib/project/project-view-constants.ts @@ -1,6 +1,9 @@ import React, { ReactNode, Suspense } from 'react'; import { InlineSuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; import i18n from '@/i18n'; +import { hasFinanceViewPermission } from '@/utils/finance-permissions'; +import { ILocalSession } from '@/types/auth/local-session.types'; +import { IProjectViewModel } from '@/types/project/projectViewModel.types'; // Import core components synchronously to avoid suspense in main tabs import ProjectViewEnhancedBoard from '@/pages/projects/projectView/enhancedBoard/project-view-enhanced-board'; @@ -164,3 +167,21 @@ export const updateTabLabels = () => { console.error('Error updating tab labels:', error); } }; + +// Function to get filtered tab items based on user permissions +export const getFilteredTabItems = ( + currentSession: ILocalSession | null, + currentProject?: IProjectViewModel | null +): TabItems[] => { + const hasFinancePermission = hasFinanceViewPermission(currentSession, currentProject); + + return tabItems.filter(item => { + // Always show all tabs except finance + if (item.key !== 'finance') { + return true; + } + + // Only show finance tab if user has permission + return hasFinancePermission; + }); +}; diff --git a/worklenz-frontend/src/pages/projects/projectView/project-view.tsx b/worklenz-frontend/src/pages/projects/projectView/project-view.tsx index 7361fd50..3679e600 100644 --- a/worklenz-frontend/src/pages/projects/projectView/project-view.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/project-view.tsx @@ -28,7 +28,7 @@ import { resetSelection } from '@/features/task-management/selection.slice'; import { resetFields } from '@/features/task-management/taskListFields.slice'; import { fetchLabels } from '@/features/taskAttributes/taskLabelSlice'; import { deselectAll } from '@/features/projects/bulkActions/bulkActionSlice'; -import { tabItems, updateTabLabels } from '@/lib/project/project-view-constants'; +import { tabItems, updateTabLabels, getFilteredTabItems } from '@/lib/project/project-view-constants'; import { setSelectedTaskId, setShowTaskDrawer, @@ -39,6 +39,7 @@ import { setProjectId as setInsightsProjectId } from '@/features/projects/insigh import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback'; import { useTranslation } from 'react-i18next'; import { useTimerInitialization } from '@/hooks/useTimerInitialization'; +import { useAuthService } from '@/hooks/useAuth'; // Import critical components synchronously to avoid suspense interruptions import TaskDrawer from '@components/task-drawer/task-drawer'; @@ -73,14 +74,21 @@ const ProjectView = React.memo(() => { // Optimize document title updates useDocumentTitle(selectedProject?.name || t('projectView')); + // Get auth service and current session + const authService = useAuthService(); + const currentSession = useMemo(() => authService.getCurrentSession(), [authService]); + // Memoize URL params to prevent unnecessary state updates const urlParams = useMemo( - () => ({ - tab: searchParams.get('tab') || tabItems[0].key, - pinnedTab: searchParams.get('pinned_tab') || '', - taskId: searchParams.get('task') || '', - }), - [searchParams] + () => { + const filteredTabItems = getFilteredTabItems(currentSession, selectedProject); + return { + tab: searchParams.get('tab') || filteredTabItems[0]?.key || 'tasks-list', + pinnedTab: searchParams.get('pinned_tab') || '', + taskId: searchParams.get('task') || '', + }; + }, + [searchParams, currentSession, selectedProject] ); const [activeTab, setActiveTab] = useState(urlParams.tab); @@ -266,7 +274,8 @@ const ProjectView = React.memo(() => { return []; } - const menuItems = tabItems.map(item => ({ + const filteredTabItems = getFilteredTabItems(currentSession, selectedProject); + const menuItems = filteredTabItems.map(item => ({ key: item.key, label: ( @@ -320,7 +329,7 @@ const ProjectView = React.memo(() => { })); return menuItems; - }, [pinnedTab, pinToDefaultTab, t, translationsReady]); + }, [pinnedTab, pinToDefaultTab, t, translationsReady, currentSession, selectedProject]); // Optimized secondary components loading with better UX const [shouldLoadSecondaryComponents, setShouldLoadSecondaryComponents] = useState(false); From e0f268e4a147d0d3bceec4f6b70670c51d0c6a3c Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Mon, 28 Jul 2025 07:41:29 +0530 Subject: [PATCH 2/2] fix(holiday-controller): specify error type in catch block for better type safety --- worklenz-backend/src/controllers/holiday-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worklenz-backend/src/controllers/holiday-controller.ts b/worklenz-backend/src/controllers/holiday-controller.ts index 11e00e15..6e0436a0 100644 --- a/worklenz-backend/src/controllers/holiday-controller.ts +++ b/worklenz-backend/src/controllers/holiday-controller.ts @@ -348,7 +348,7 @@ export default class HolidayController extends WorklenzControllerBase { totalPopulated++; } } - } catch (error) { + } catch (error: any) { errors.push(`${country.name}: ${error.message}`); } }