Skip to content

Commit

Permalink
Merge pull request #2100 from ever-co/feat/activity
Browse files Browse the repository at this point in the history
Feat: Display Worked Tasks to Activity section in Team Member Card
  • Loading branch information
evereq authored Jan 18, 2024
2 parents 0e2eda4 + 323dc5a commit aff82c1
Show file tree
Hide file tree
Showing 18 changed files with 282 additions and 132 deletions.
41 changes: 22 additions & 19 deletions apps/web/app/[locale]/profile/[memberId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,24 @@ import { ArrowLeft } from 'lib/components/svgs';
import { TaskFilter, Timer, TimerStatus, UserProfileTask, getTimerStatusValue, useTaskFilter } from 'lib/features';
import { MainHeader, MainLayout } from 'lib/layout';
import Link from 'next/link';
import { useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslations } from 'next-intl';
import stc from 'string-to-color';

import { useRecoilValue } from 'recoil';
import { fullWidthState } from '@app/stores/fullWidth';
import { ActivityFilters } from '@app/constants';
import { ScreenshootTab } from 'lib/features/activity/screenshoots';
import { AppsTab } from 'lib/features/activity/apps';
import { VisitedSitesTab } from 'lib/features/activity/visited-sites';

const Profile = ({ params }: { params: { memberId: string } }) => {
type FilterTab = 'Tasks' | 'Screenshots' | 'Apps' | 'Visited Sites';

const Profile = React.memo(function ProfilePage({ params }: { params: { memberId: string } }) {
const profile = useUserProfilePage();
const { user } = useAuthenticateUser();
const { isTrackingEnabled, activeTeam } = useOrganizationTeams();
const fullWidth = useRecoilValue(fullWidthState);
const [activityFilter, setActivityFilter] = useState<ActivityFilters>(ActivityFilters.TASKS);
const [activityFilter, setActivityFilter] = useState<FilterTab>('Tasks');

const hook = useTaskFilter(profile);
const canSeeActivity = profile.userProfile?.id === user?.id || user?.role?.name?.toUpperCase() == 'MANAGER';
Expand All @@ -39,11 +40,23 @@ const Profile = ({ params }: { params: { memberId: string } }) => {
{ title: JSON.parse(t('pages.profile.BREADCRUMB')) || '', href: `/profile/${params.memberId}` }
];

console.log({ activityFilter });
const activityScreens = {
Tasks: <UserProfileTask profile={profile} tabFiltered={hook} />,
Screenshots: <ScreenshootTab />,
Apps: <AppsTab />,
'Visited Sites': <VisitedSitesTab />
};

const profileIsAuthUser = useMemo(() => profile.isAuthUser, [profile.isAuthUser]);
const hookFilterType = useMemo(() => hook.filterType, [hook.filterType]);

const changeActivityFilter = useCallback(
(filter: FilterTab) => {
setActivityFilter(filter);
},
[setActivityFilter]
);

return (
<>
<MainLayout showTimer={!profileIsAuthUser && isTrackingEnabled}>
Expand Down Expand Up @@ -80,15 +93,15 @@ const Profile = ({ params }: { params: { memberId: string } }) => {
{hook.tab == 'worked' && canSeeActivity && (
<Container fullWidth={fullWidth} className="py-8">
<div className={clsxm('flex justify-start items-center gap-4')}>
{Object.values(ActivityFilters).map((filter: ActivityFilters, i) => (
{Object.keys(activityScreens).map((filter, i) => (
<div key={i} className="flex cursor-pointer justify-start items-center gap-4">
{i !== 0 && <VerticalSeparator />}
<div
className={clsxm(
'text-gray-500',
activityFilter == filter && 'text-black dark:text-white'
)}
onClick={() => setActivityFilter(filter)}
onClick={() => changeActivityFilter(filter as FilterTab)}
>
{filter}
</div>
Expand All @@ -99,22 +112,12 @@ const Profile = ({ params }: { params: { memberId: string } }) => {
)}

<Container fullWidth={fullWidth} className="mb-10">
{hook.tab == 'worked' && activityFilter == ActivityFilters.TASKS ? (
<UserProfileTask profile={profile} tabFiltered={hook} />
) : hook.tab == 'worked' && canSeeActivity && activityFilter == ActivityFilters.SCREENSHOOTS ? (
<ScreenshootTab />
) : hook.tab == 'worked' && canSeeActivity && activityFilter == ActivityFilters.APPS ? (
<AppsTab />
) : hook.tab == 'worked' && canSeeActivity && activityFilter == ActivityFilters.VISITED_SITES ? (
<VisitedSitesTab />
) : (
<UserProfileTask profile={profile} tabFiltered={hook} />
)}
{activityScreens[activityFilter] ?? null}
</Container>
</MainLayout>
</>
);
};
});

function UserProfileDetail({ member }: { member?: OT_Member }) {
const user = useMemo(() => member?.employee.user, [member?.employee.user]);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/[locale]/task/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const TaskDetails = () => {
{/* <IssueCard related={true} /> */}

{/* <CompletionBlock /> */}
{/* <ActivityBlock /> */}
{/* <TaskActivity /> */}
</div>
</section>
<div className="flex flex-col mt-4 lg:mt-0 3xl:min-w-[24rem] w-full lg:w-[30%]">
Expand Down
8 changes: 4 additions & 4 deletions apps/web/app/hooks/features/useOrganizationTeams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,14 @@ export function useOrganizationTeams() {

const setActiveTeam = useCallback(
(team: (typeof teams)[0]) => {
setActiveTeamIdCookie(team.id);
setOrganizationIdCookie(team.organizationId);
setActiveTeamIdCookie(team?.id);
setOrganizationIdCookie(team?.organizationId);
// This must be called at the end (Update store)
setActiveTeamId(team.id);
setActiveTeamId(team?.id);

// Set Project Id to cookie
// TODO: Make it dynamic when we add Dropdown in Navbar
if (team && team.projects && team.projects.length) {
if (team && team?.projects && team.projects.length) {
setActiveProjectIdCookie(team.projects[0].id);
}
},
Expand Down
47 changes: 19 additions & 28 deletions apps/web/app/hooks/features/useTimeDailyActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,34 @@

import { useCallback, useEffect } from 'react';
import { useQuery } from '../useQuery';
import { useRecoilState } from 'recoil';
import { timeAppVisitedDetail, timeAppsState, timeVisitedSitesState } from '@app/stores/time-slot';
import { useRecoilState, useRecoilValue } from 'recoil';
import { timeAppsState, timeVisitedSitesState } from '@app/stores/time-slot';
import moment from 'moment';
import { useAuthenticateUser } from './useAuthenticateUser';
import { getTimerDailyRequestAPI } from '@app/services/client/api';
import { useUserProfilePage } from './useUserProfilePage';
import { activityTypeState } from '@app/stores/activity-type';

export function useTimeDailyActivity(type: string, id?: string) {
const profile = useUserProfilePage();
export function useTimeDailyActivity(type?: string) {
const { user } = useAuthenticateUser();
const [visitedApps, setVisitedApps] = useRecoilState(timeAppsState);
const [visitedAppDetail, setVisitedAppDetail] = useRecoilState(timeAppVisitedDetail);
const activityFilter = useRecoilValue(activityTypeState);
const [visitedSites, setVisitedSites] = useRecoilState(timeVisitedSitesState);

const { loading, queryCall } = useQuery(getTimerDailyRequestAPI);

const getVisitedApps = useCallback(
({ title }: { title?: string }) => {
(title?: string) => {
const todayStart = moment().startOf('day').toDate();
const todayEnd = moment().endOf('day').toDate();
const employeeId = id ?? profile.member?.employeeId ?? '';
if (profile.userProfile?.id === user?.id || user?.role?.name?.toUpperCase() == 'MANAGER') {
const employeeId = activityFilter.member ? activityFilter.member?.employeeId : user?.employee?.id;
if (
activityFilter.member?.employeeId === user?.employee.id ||
user?.role?.name?.toUpperCase() == 'MANAGER'
) {
queryCall({
tenantId: user?.tenantId ?? '',
organizationId: user?.employee.organizationId ?? '',
employeeId: employeeId,
employeeId: employeeId ?? '',
todayEnd,
type,
todayStart,
Expand All @@ -36,37 +38,26 @@ export function useTimeDailyActivity(type: string, id?: string) {
.then((response) => {
if (response.data) {
// @ts-ignore
if (title) setVisitedAppDetail(response.data[0]);
else if (type == 'APP') setVisitedApps(response.data);
// if (title) setVisitedAppDetail(response.data[0]);
if (type == 'APP') setVisitedApps(response.data);
else setVisitedSites(response.data);
}
})
.catch((err) => console.log(err));
}
},
[
profile.member?.employeeId,
profile.userProfile?.id,
user?.id,
user?.role?.name,
user?.tenantId,
user?.employee.organizationId,
queryCall,
type,
setVisitedAppDetail,
setVisitedApps,
setVisitedSites
]
// eslint-disable-next-line react-hooks/exhaustive-deps
[queryCall, type]
);

useEffect(() => {
getVisitedApps({});
}, [user, getVisitedApps]);
getVisitedApps();
}, [getVisitedApps]);

return {
visitedApps,
visitedSites,
visitedAppDetail,
// visitedAppDetail,
getVisitedApps,
loading
};
Expand Down
25 changes: 9 additions & 16 deletions apps/web/app/hooks/features/useTimeSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

import { useCallback, useEffect } from 'react';
import { useQuery } from '../useQuery';
import { useRecoilState } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { timeSlotsState } from '@app/stores/time-slot';
import moment from 'moment';
import { useAuthenticateUser } from './useAuthenticateUser';
import { deleteTimerLogsRequestAPI, getTimerLogsRequestAPI } from '@app/services/client/api';
import { useUserProfilePage } from './useUserProfilePage';
import { activityTypeState } from '@app/stores/activity-type';

export function useTimeSlots(id?: string) {
export function useTimeSlots(hasFilter?: boolean) {
const { user } = useAuthenticateUser();
const [timeSlots, setTimeSlots] = useRecoilState(timeSlotsState);
const activityFilter = useRecoilValue(activityTypeState);
const profile = useUserProfilePage();

const { loading, queryCall } = useQuery(getTimerLogsRequestAPI);
Expand All @@ -20,8 +22,8 @@ export function useTimeSlots(id?: string) {
const getTimeSlots = useCallback(() => {
const todayStart = moment().startOf('day').toDate();
const todayEnd = moment().endOf('day').toDate();
const employeeId = id ? id : profile.member?.employeeId ;
if ( profile.userProfile?.id === user?.id || user?.role?.name?.toUpperCase() == 'MANAGER') {
const employeeId = activityFilter.member ? activityFilter.member?.employeeId : user?.employee?.id;
if (activityFilter.member?.employeeId === user?.employee.id || user?.role?.name?.toUpperCase() == 'MANAGER') {
queryCall({
tenantId: user?.tenantId ?? '',
organizationId: user?.employee.organizationId ?? '',
Expand All @@ -35,17 +37,8 @@ export function useTimeSlots(id?: string) {
}
});
}
}, [
id,
profile.member?.employeeId,
profile.userProfile?.id,
user?.id,
user?.role?.name,
user?.tenantId,
user?.employee.organizationId,
queryCall,
setTimeSlots
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasFilter, activityFilter.member?.employeeId, profile.member?.employeeId, user?.id, queryCall, setTimeSlots]);

const deleteTimeSlots = useCallback(
(ids: string[]) => {
Expand All @@ -63,7 +56,7 @@ export function useTimeSlots(id?: string) {

useEffect(() => {
getTimeSlots();
}, [user, getTimeSlots]);
}, [getTimeSlots]);

return {
timeSlots,
Expand Down
66 changes: 66 additions & 0 deletions apps/web/app/hooks/features/useUserDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client';

import { ITeamTask } from '@app/interfaces';
import { useCallback, useEffect } from 'react';
import { useAuthenticateUser } from './useAuthenticateUser';
import { useAuthTeamTasks } from './useAuthTeamTasks';
import { useOrganizationTeams } from './useOrganizationTeams';
import { useTaskStatistics } from './useTaskStatistics';
import { useTeamTasks } from './useTeamTasks';

export function useUserDetails(memberId: string) {
const { activeTeam } = useOrganizationTeams();
const { activeTeamTask, updateTask } = useTeamTasks();

const { user: auth } = useAuthenticateUser();
const { getTasksStatsData } = useTaskStatistics();

const members = activeTeam?.members || [];

const matchUser = members.find((m) => {
return m.employee.userId === memberId;
});

const isAuthUser = auth?.employee?.userId === memberId;

const activeUserTeamTask = isAuthUser ? activeTeamTask : matchUser?.lastWorkedTask;

const userProfile = isAuthUser ? auth : matchUser?.employee.user;

const employeeId = isAuthUser ? auth?.employee?.id : matchUser?.employeeId;

/* Filtering the tasks */
const tasksGrouped = useAuthTeamTasks(userProfile);

useEffect(() => {
if (employeeId) {
getTasksStatsData(employeeId);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [employeeId]);

const assignTask = useCallback(
(task: ITeamTask) => {
if (!matchUser?.employeeId) {
return Promise.resolve();
}

return updateTask({
...task,
members: [...task.members, (matchUser?.employeeId ? { id: matchUser?.employeeId } : {}) as any]
});
},
[updateTask, matchUser]
);

return {
isAuthUser,
activeUserTeamTask,
userProfile,
tasksGrouped,
member: matchUser,
assignTask
};
}

export type I_UserProfilePage = ReturnType<typeof useUserDetails>;
3 changes: 2 additions & 1 deletion apps/web/app/hooks/features/useUserProfilePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export function useUserProfilePage() {
if (employeeId) {
getTasksStatsData(employeeId);
}
}, [getTasksStatsData, employeeId]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [employeeId]);

const assignTask = useCallback(
(task: ITeamTask) => {
Expand Down
9 changes: 9 additions & 0 deletions apps/web/app/interfaces/IActivityFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { OT_Member } from './IOrganizationTeam';

export interface IActivityFilter {
type: 'DATE' | 'TICKET';
member: OT_Member | null;
taskId?: string;
dateStart?: Date;
dateStop?: Date;
}
9 changes: 5 additions & 4 deletions apps/web/app/services/client/api/activity/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function getTimerDailyRequestAPI({
employeeId: string;
todayEnd: Date;
todayStart: Date;
type: string;
type?: string | undefined;
title?: string;
}) {
const params: {
Expand All @@ -25,18 +25,19 @@ export async function getTimerDailyRequestAPI({
'employeeIds[0]': string;
startDate: string;
endDate: string;
'types[0]': string;
'types[0]'?: string;
'title[0]'?: string;
} = {
tenantId: tenantId,
organizationId: organizationId,
'employeeIds[0]': employeeId,
startDate: todayStart.toISOString(),
endDate: todayEnd.toISOString(),
'types[0]': type
endDate: todayEnd.toISOString()
};
if (type) params['types[0]'] = type;
if (title) params['title[0]'] = title;
const query = new URLSearchParams(params);
console.log('QUERY', query);
const endpoint = GAUZY_API_BASE_SERVER_URL.value
? `/timesheet/activity/daily?${query.toString()}`
: `/timer/daily?${query.toString()}`;
Expand Down
Loading

0 comments on commit aff82c1

Please sign in to comment.