From 60a3d0f241fe4af827bade8b850bac0e4d50cb31 Mon Sep 17 00:00:00 2001 From: "Gloire Mutaliko (Salva)" <86450367+GloireMutaliko21@users.noreply.github.com> Date: Sun, 19 May 2024 11:54:16 +0200 Subject: [PATCH] Member profile page hide some tab options (#2518) * fix: member profile page hide some tab options * feat: integrate share profile setting * feat: plans and worked views by settings * feat: plans and worked views by settings * fix: build issues --- .../services/interfaces/IOrganizationTeam.ts | 3 + .../app/hooks/features/useAuthenticateUser.ts | 18 ++++++ apps/web/app/interfaces/IOrganizationTeam.ts | 3 + apps/web/lib/components/switch.tsx | 56 ++++++++++++++++++- .../features/task/daily-plan/future-tasks.tsx | 31 ++++++---- apps/web/lib/features/task/task-card.tsx | 37 ++++++++---- apps/web/lib/features/task/task-filters.tsx | 33 +++++++---- apps/web/lib/features/user-profile-plans.tsx | 36 +++++++----- apps/web/lib/settings/integration-setting.tsx | 2 +- apps/web/lib/settings/team-setting-form.tsx | 27 ++++++--- 10 files changed, 187 insertions(+), 59 deletions(-) diff --git a/apps/mobile/app/services/interfaces/IOrganizationTeam.ts b/apps/mobile/app/services/interfaces/IOrganizationTeam.ts index 4bcc9e04e..ee8627666 100644 --- a/apps/mobile/app/services/interfaces/IOrganizationTeam.ts +++ b/apps/mobile/app/services/interfaces/IOrganizationTeam.ts @@ -11,6 +11,7 @@ export interface IOrganizationTeamCreate { tags?: any[]; organizationId: string; tenantId: string; + shareProfileView?: boolean; public?: boolean; imageId?: string | null; image?: IImageAssets | null; @@ -30,6 +31,7 @@ export interface IOrganizationTeam { members: any[]; tags: any[]; id: string; + shareProfileView?: boolean; createdAt: string; updatedAt: string; imageId?: string | null; @@ -48,6 +50,7 @@ export interface IOrganizationTeamList { members: OT_Member[]; managerIds?: string[]; memberIds?: string[]; + shareProfileView?: boolean; public?: boolean; createdById: string; createdBy: IUser; diff --git a/apps/web/app/hooks/features/useAuthenticateUser.ts b/apps/web/app/hooks/features/useAuthenticateUser.ts index 0dcd403bf..9d6233dc6 100644 --- a/apps/web/app/hooks/features/useAuthenticateUser.ts +++ b/apps/web/app/hooks/features/useAuthenticateUser.ts @@ -10,6 +10,8 @@ import { useRecoilState } from 'recoil'; import { useQuery } from '../useQuery'; import { useIsMemberManager } from './useTeamMember'; +import { useOrganizationTeams } from './useOrganizationTeams'; +import { useUserProfilePage } from './useUserProfilePage'; export const useAuthenticateUser = (defaultUser?: IUser) => { const [user, setUser] = useRecoilState(userState); @@ -68,3 +70,19 @@ export const useAuthenticateUser = (defaultUser?: IUser) => { refreshToken }; }; + +/** + * A hook to check if the current user is a manager or whom the current profile belongs to + * + * @description We need, especially for the user profile page, to know if the current user can see some activities, or interact with data + * @returns a boolean that defines in the user is authorized + */ + +export const useCanSeeActivityScreen = () => { + const { user } = useAuthenticateUser(); + const { activeTeamManagers } = useOrganizationTeams(); + const profile = useUserProfilePage(); + + const isManagerConnectedUser = activeTeamManagers.findIndex((member) => member.employee?.user?.id == user?.id); + return profile.userProfile?.id === user?.id || isManagerConnectedUser != -1; +}; diff --git a/apps/web/app/interfaces/IOrganizationTeam.ts b/apps/web/app/interfaces/IOrganizationTeam.ts index b609dd051..cb392b8c1 100644 --- a/apps/web/app/interfaces/IOrganizationTeam.ts +++ b/apps/web/app/interfaces/IOrganizationTeam.ts @@ -15,6 +15,7 @@ export interface IOrganizationTeamCreate { tags?: any[]; organizationId: string; tenantId: string; + shareProfileView?: boolean; public?: boolean; imageId?: string | null; image?: IImageAssets | null; @@ -42,6 +43,7 @@ export interface IOrganizationTeam { id: string; createdAt: string; updatedAt: string; + shareProfileView?: boolean; imageId?: string | null; image?: IImageAssets | null; } @@ -59,6 +61,7 @@ export interface IOrganizationTeamList { updated?: boolean; prefix: string; members: OT_Member[]; + shareProfileView?: boolean; public?: boolean; createdById: string; createdBy: IUser; diff --git a/apps/web/lib/components/switch.tsx b/apps/web/lib/components/switch.tsx index 6e39e09a2..5f421c857 100644 --- a/apps/web/lib/components/switch.tsx +++ b/apps/web/lib/components/switch.tsx @@ -1,6 +1,6 @@ import { useOrganizationTeams } from '@app/hooks'; import { useOrganizationEmployeeTeams } from '@app/hooks/features/useOrganizatioTeamsEmployee'; -import { OT_Member } from '@app/interfaces'; +import { OT_Member, RoleNameEnum } from '@app/interfaces'; import { Switch } from '@headlessui/react'; import { useCallback, useEffect, useState } from 'react'; import { Text } from './typography'; @@ -49,3 +49,57 @@ export default function TimeTrackingToggle({ activeManager }: { activeManager: O ); } + +export function ShareProfileViewsToggle() { + const t = useTranslations(); + const { activeTeam, editOrganizationTeam } = useOrganizationTeams(); + const [enabled, setEnabled] = useState(activeTeam?.shareProfileView); + + const handleChange = useCallback(async () => { + if (activeTeam && typeof enabled != 'undefined') { + await editOrganizationTeam({ + ...activeTeam, + shareProfileView: !enabled, + memberIds: activeTeam.members + .map((t) => t.employee.id) + .filter((value, index, array) => array.indexOf(value) === index), + managerIds: activeTeam.members + .filter( + (m) => + m.role && + (m.role.name === RoleNameEnum.MANAGER || + m.role.name === RoleNameEnum.SUPER_ADMIN || + m.role.name === RoleNameEnum.ADMIN) + ) + .map((t) => t.employee.id) + .filter((value, index, array) => array.indexOf(value) === index) + }); + setEnabled(!enabled); + } + }, [activeTeam, editOrganizationTeam, enabled]); + + useEffect(() => { + setEnabled(activeTeam?.shareProfileView); + }, [activeTeam?.shareProfileView]); + + return ( +
+ { + handleChange(); + }} + className={`${enabled ? 'bg-[#DBD3FA]' : 'bg-[#80808061]'} + relative inline-flex h-[38px] w-[74px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`} + > + {t('common.USE_SETTING')} + + {enabled ? t('common.ACTIVATED') : t('common.DEACTIVATED')} +
+ ); +} diff --git a/apps/web/lib/features/task/daily-plan/future-tasks.tsx b/apps/web/lib/features/task/daily-plan/future-tasks.tsx index 46b6ceeb7..eda300ea7 100644 --- a/apps/web/lib/features/task/daily-plan/future-tasks.tsx +++ b/apps/web/lib/features/task/daily-plan/future-tasks.tsx @@ -4,7 +4,7 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@c import { EmptyPlans, PlanHeader } from 'lib/features/user-profile-plans'; import { TaskCard } from '../task-card'; import { Button } from '@components/ui/button'; -import { useDailyPlan } from '@app/hooks'; +import { useCanSeeActivityScreen, useDailyPlan } from '@app/hooks'; import { ReloadIcon } from '@radix-ui/react-icons'; export function FutureTasks({ dayPlans, profile }: { dayPlans: IDailyPlan[]; profile: any }) { @@ -16,6 +16,7 @@ export function FutureTasks({ dayPlans, profile }: { dayPlans: IDailyPlan[]; pro return planDate.getTime() >= today.getTime(); }); const { deleteDailyPlan, deleteDailyPlanLoading } = useDailyPlan(); + const canSeeActivity = useCanSeeActivityScreen(); return (
@@ -60,17 +61,23 @@ export function FutureTasks({ dayPlans, profile }: { dayPlans: IDailyPlan[]; pro {/* Delete Plan */} -
- -
+ {canSeeActivity ? ( +
+ +
+ ) : ( + <> + )} ))} diff --git a/apps/web/lib/features/task/task-card.tsx b/apps/web/lib/features/task/task-card.tsx index a5d0b9282..e490f2418 100644 --- a/apps/web/lib/features/task/task-card.tsx +++ b/apps/web/lib/features/task/task-card.tsx @@ -4,6 +4,7 @@ import { secondsToTime } from '@app/helpers'; import { I_TeamMemberCardHook, I_UserProfilePage, + useCanSeeActivityScreen, useDailyPlan, useModal, useOrganizationEmployeeTeams, @@ -476,6 +477,8 @@ function TaskCardMenu({ } }, [memberInfo, task, viewType]); + const canSeeActivity = useCanSeeActivityScreen(); + return ( @@ -552,21 +555,33 @@ function TaskCardMenu({ )} {viewType === 'dailyplan' && planMode === 'Outstanding' && ( - + <> + {canSeeActivity ? ( + + ) : ( + <> + )} + )} {viewType === 'dailyplan' && (planMode === 'Today Tasks' || planMode === 'Future Tasks') && ( -
- -
- -
-
+ <> + {canSeeActivity ? ( +
+ +
+ +
+
+ ) : ( + <> + )} + )} {/*
  • member.employee?.user?.id == user?.id); + const canSeeActivity = profile.userProfile?.id === user?.id || isManagerConnectedUser != -1; + const [tab, setTab] = useState(defaultValue || 'worked'); const [filterType, setFilterType] = useState(undefined); @@ -67,12 +72,6 @@ export function useTaskFilter(profile: I_UserProfilePage) { }); const tabs: ITabs[] = [ - { - tab: 'worked', - name: t('common.WORKED'), - description: t('task.tabFilter.WORKED_DESCRIPTION'), - count: profile.tasksGrouped.workedTasks.length - }, { tab: 'assigned', name: t('common.ASSIGNED'), @@ -84,14 +83,24 @@ export function useTaskFilter(profile: I_UserProfilePage) { name: t('common.UNASSIGNED'), description: t('task.tabFilter.UNASSIGNED_DESCRIPTION'), count: profile.tasksGrouped.unassignedTasks.length - }, - { + } + ]; + + // For tabs on profile page, display "Worked" and "Daily Plan" only for the logged in user or managers + if (activeTeam?.shareProfileView || canSeeActivity) { + tabs.push({ tab: 'dailyplan', name: 'Daily Plan', description: 'This tab shows all yours tasks planned', count: profile.tasksGrouped.dailyplan?.length - } - ]; + }); + tabs.unshift({ + tab: 'worked', + name: t('common.WORKED'), + description: t('task.tabFilter.WORKED_DESCRIPTION'), + count: profile.tasksGrouped.workedTasks.length + }); + } useEffect(() => { window.localStorage.setItem('task-tab', tab); diff --git a/apps/web/lib/features/user-profile-plans.tsx b/apps/web/lib/features/user-profile-plans.tsx index 330bd610f..73a2a76b5 100644 --- a/apps/web/lib/features/user-profile-plans.tsx +++ b/apps/web/lib/features/user-profile-plans.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useRecoilValue } from 'recoil'; -import { useDailyPlan, useUserProfilePage } from '@app/hooks'; +import { useCanSeeActivityScreen, useDailyPlan, useUserProfilePage } from '@app/hooks'; import { TaskCard } from './task/task-card'; import { IDailyPlan } from '@app/interfaces'; import { Container, NoData, ProgressBar, VerticalSeparator } from 'lib/components'; @@ -87,6 +87,8 @@ function AllPlans({ const { deleteDailyPlan, deleteDailyPlanLoading } = useDailyPlan(); + const canSeeActivity = useCanSeeActivityScreen(); + return (
    {filteredPlans.length > 0 ? ( @@ -135,19 +137,25 @@ function AllPlans({ {/* Delete Plan */} {currentTab === 'Today Tasks' && ( -
    - -
    + <> + {canSeeActivity ? ( +
    + +
    + ) : ( + <> + )} + )} diff --git a/apps/web/lib/settings/integration-setting.tsx b/apps/web/lib/settings/integration-setting.tsx index 647bdbc83..926e55207 100644 --- a/apps/web/lib/settings/integration-setting.tsx +++ b/apps/web/lib/settings/integration-setting.tsx @@ -307,7 +307,7 @@ export const IntegrationSetting = () => { {selectedRepo && ( )}
    diff --git a/apps/web/lib/settings/team-setting-form.tsx b/apps/web/lib/settings/team-setting-form.tsx index 8aa91f7eb..f29d908c5 100644 --- a/apps/web/lib/settings/team-setting-form.tsx +++ b/apps/web/lib/settings/team-setting-form.tsx @@ -3,7 +3,7 @@ import { RoleNameEnum } from '@app/interfaces'; import { userState } from '@app/stores'; import { Button, ColorPicker, InputField, Text, Tooltip } from 'lib/components'; import { EmojiPicker } from 'lib/components/emoji-picker'; -import TimeTrackingToggle from 'lib/components/switch'; +import TimeTrackingToggle, { ShareProfileViewsToggle } from 'lib/components/switch'; import debounce from 'lodash/debounce'; import isEqual from 'lodash/isEqual'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -93,6 +93,7 @@ export const TeamSettingForm = () => { public: values.teamType === 'PUBLIC' ? true : false, color: values.color, emoji: values.emoji, + shareProfileView: activeTeam.shareProfileView, teamSize: values.teamSize, memberIds: activeTeam.members .map((t) => t.employee.id) @@ -359,14 +360,24 @@ export const TeamSettingForm = () => { {/* Time Tracking */} {isTeamManager ? ( -
    - - {t('pages.settingsTeam.TIME_TRACKING')} - -
    - + <> +
    + + {t('pages.settingsTeam.TIME_TRACKING')} + +
    + +
    -
    +
    + + Share members profile views + +
    + +
    +
    + ) : ( <> )}