Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: user Activity on Task ( Hooks, API Routes) #2124

Merged
merged 9 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@
"Screenshoot",
"screenshoots",
"tota",
"Intervall"
"Intervall",
"Timesheets"
],
"useGitignore": true,
"ignorePaths": [
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 @@ -85,7 +85,7 @@ const TaskDetails = () => {
{/* <IssueCard related={true} /> */}

{/* <CompletionBlock /> */}
<TaskActivity />
{task && <TaskActivity task={task} />}
</div>
</section>
<div className="flex flex-col mt-4 lg:mt-0 3xl:min-w-[24rem] w-full lg:w-[30%]">
Expand Down
23 changes: 21 additions & 2 deletions apps/web/app/api/timer/timesheet/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
import { authenticatedGuard } from '@app/services/server/guards/authenticated-guard-app';
import { taskActivityRequest } from '@app/services/server/requests';
import { NextResponse } from 'next/server';

export async function GET(req: Request) {
const res = new NextResponse();
const { $res, user } = await authenticatedGuard(req, res);
if (!user) return $res('unauthorized');
const { $res, user, tenantId, organizationId, access_token } = await authenticatedGuard(req, res);
if (!user) return $res('Unauthorized');

const { searchParams } = new URL(req.url);

const { taskId } = searchParams as unknown as {
taskId: string;
};

const { data } = await taskActivityRequest(
{
'taskIds[0]': taskId,
tenantId,
organizationId,
defaultRange: 'false'
},
access_token
);

return $res(data);
}
21 changes: 21 additions & 0 deletions apps/web/app/helpers/array-data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ITimerSlot } from '@app/interfaces/timer/ITimerSlot';
import { pad } from './number';
import { ITimerApps } from '@app/interfaces/timer/ITimerApp';
import { ITaskTimesheet } from '@app/interfaces';
export function groupDataByHour(data: ITimerSlot[]) {
const groupedData: { startedAt: string; stoppedAt: string; items: ITimerSlot[] }[] = [];

Expand Down Expand Up @@ -47,6 +48,26 @@ export function groupAppsByHour(apps: ITimerApps[]) {
return groupedData.sort((a, b) => (new Date(a.hour) > new Date(b.hour) ? -1 : 1));
}

export function groupByTime(data: ITaskTimesheet[]) {
const groupedData: { date: string; items: ITaskTimesheet[] }[] = [];

data.forEach((item) => {
const date = new Date(item.date).toDateString();

const dateDataIndex = groupedData.findIndex((el) => el.date == date);

if (dateDataIndex !== -1) {
groupedData[dateDataIndex].items.push(item);
} else
groupedData.push({
date,
items: [item]
});
});

return groupedData.sort((a, b) => (new Date(a.date) > new Date(b.date) ? -1 : 1));
}

const formatTime = (d: Date | string, addHour: boolean) => {
d = d instanceof Date ? d : new Date(d);
if (addHour)
Expand Down
46 changes: 46 additions & 0 deletions apps/web/app/hooks/features/useTaskActivity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client';

import { useCallback, useEffect } from 'react';
import { useQuery } from '../useQuery';
import { useRecoilState, useRecoilValue } from 'recoil';

import { useAuthenticateUser } from './useAuthenticateUser';
import { useUserProfilePage } from './useUserProfilePage';
import { activityTypeState } from '@app/stores/activity-type';
import { taskTimesheetState } from '@app/stores/task-timesheet';
import { getTaskTimesheetRequestAPI } from '@app/services/client/api';

export function useTaskTimeSheets(id: string) {
const { user } = useAuthenticateUser();
const [taskTimesheets, setTaskTimesheets] = useRecoilState(taskTimesheetState);
const activityFilter = useRecoilValue(activityTypeState);
const profile = useUserProfilePage();

const { loading, queryCall } = useQuery(getTaskTimesheetRequestAPI);
const getTaskTimesheets = useCallback(() => {
if (activityFilter.member?.employeeId === user?.employee.id || user?.role?.name?.toUpperCase() == 'MANAGER') {
queryCall({
tenantId: user?.tenantId ?? '',
organizationId: user?.employee.organizationId ?? '',
taskId: id
}).then((response) => {
console.log(response);
if (response.data) {
console.log(response.data);
setTaskTimesheets(response.data);
}
});
} else setTaskTimesheets([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activityFilter.member?.employeeId, profile.member?.employeeId, user?.id, queryCall, setTaskTimesheets]);

useEffect(() => {
getTaskTimesheets();
}, [getTaskTimesheets]);

return {
taskTimesheets,
getTaskTimesheets,
loading
};
}
24 changes: 24 additions & 0 deletions apps/web/app/interfaces/ITaskTimesheet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { IEmployee } from './IEmployee';
import { IProject } from './IProject';
import { ITeamTask } from './ITask';
import { ITimerSlot } from './timer/ITimerSlot';

export interface ITaskTimesheet {
id: string
title: string;
description?: string;
metaData?: string;
date: Date;
time: string;
duration?: number;
type?: string;
source?: string;
employee?: IEmployee;
employeeId?: IEmployee['id'];
project?: IProject;
projectId?: IProject['id'];
timeSlot?: ITimerSlot;
timeSlotId?: ITimerSlot['id'];
task?: ITeamTask;
taskId?: ITeamTask['id'];
}
1 change: 1 addition & 0 deletions apps/web/app/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './ITaskStatus';
export * from './ITaskVersion';
export * from './ITaskPriorities';
export * from './ITaskSizes';
export * from './ITaskTimesheet';
export * from './ITaskLabels';
export * from './ITaskRelatedIssueType';
export * from './IColor';
Expand Down
37 changes: 37 additions & 0 deletions apps/web/app/services/client/api/activity/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ITaskTimesheet } from '@app/interfaces';
import { get } from '@app/services/client/axios';
import { GAUZY_API_BASE_SERVER_URL } from '@app/constants';

export async function getTaskTimesheetRequestAPI({
taskId,
tenantId,
organizationId,
defaultRange,
unitOfTime
}: {
tenantId: string;
organizationId: string;
defaultRange?: string;
taskId?: string;
unitOfTime?: 'day';
}) {
const params: {
tenantId: string;
organizationId: string;
defaultRange?: string;
'taskIds[0]'?: string;
unitOfTime?: 'day';
} = {
'taskIds[0]': taskId,
tenantId,
organizationId,
defaultRange,
unitOfTime
};
const query = new URLSearchParams(params);
const endpoint = GAUZY_API_BASE_SERVER_URL.value
? `/timesheet/activity?${query.toString()}`
: `/timer/timesheet?${query.toString()}`;

return get<ITaskTimesheet[]>(endpoint);
}
1 change: 1 addition & 0 deletions apps/web/app/services/client/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ export * from './organization-projects';

export * from './activity/time-slots';
export * from './activity/activity';
export * from './activity';
export * from './default';
2 changes: 1 addition & 1 deletion apps/web/app/services/client/api/organization-team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export async function getOrganizationTeamAPI(teamId: string, organizationId: str
params[`relations[${i}]`] = rl;
});

const queries = new URLSearchParams(params || {});
const queries = new URLSearchParams(params);

const endpoint = `/organization-team/${teamId}?${queries.toString()}`;

Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/services/server/requests/organization-team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function getOrganizationTeamRequest(
params[`relations[${i}]`] = rl;
});

const queries = new URLSearchParams(params || {});
const queries = new URLSearchParams(params);
return serverFetch<IOrganizationTeamWithMStatus>({
path: `/organization-team/${teamId}?${queries.toString()}`,
method: 'GET',
Expand Down
19 changes: 19 additions & 0 deletions apps/web/app/services/server/requests/timesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,22 @@ export function tasksTimesheetStatisticsRequest(params: TTasksTimesheetStatistic
tenantId: params.tenantId
});
}

export type TTaskActivityParams = {
tenantId: string;
organizationId: string;
defaultRange?: string;
'taskIds[0]'?: string;
unitOfTime?: 'day';
};

export function taskActivityRequest(params: TTaskActivityParams, bearer_token: string) {
const queries = new URLSearchParams(params);

return serverFetch<ITasksTimesheet[]>({
path: `/timesheet/activity?${queries.toString()}`,
method: 'GET',
bearer_token,
tenantId: params.tenantId
});
}
7 changes: 7 additions & 0 deletions apps/web/app/stores/task-timesheet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ITaskTimesheet } from '@app/interfaces';
import { atom } from 'recoil';

export const taskTimesheetState = atom<ITaskTimesheet[]>({
key: 'taskTimesheetState',
default: []
});
4 changes: 2 additions & 2 deletions apps/web/components/pages/maintenance/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ function Maintenance() {
const t = useTranslations();
return (
<div className="mt-28 flex flex-col gap-7 items-center">
<RocketIcon width={200} height={200} className="text-xl text-chetwodeBlue" />
<div className="m-auto relative my-6 pt-12 flex justify-center items-center gap-4 text-center ">
<div className="m-auto relative flex justify-center items-center gap-4 text-center ">
<RocketIcon width={97} height={97} className="text-xl text-chetwodeBlue" />
<Text className="text-[78px] text-center font-semibold text-chetwodeBlue">Maintenance</Text>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const TaskEstimationsInfo = () => {
</Disclosure.Button>
<Disclosure.Panel>
<div className="flex flex-col gap-[0.5625rem] mt-2">
{task?.members.map((member) => {
{task?.members?.map((member) => {
// TODO
// Enable other users estimations in v2
return (
Expand Down
44 changes: 44 additions & 0 deletions apps/web/lib/features/task/activity/user-task-activity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { clsxm } from '@app/utils';
import { Tab } from '@headlessui/react';
import { ActivityFilters } from '@app/constants';

export const UserTaskActivity = () => {
// get slots related to Task Id
// get apps visited related to Task Id
// get visited Sites related to Task Id
return (
<div className="shadow-md rounded-md p-4 my-4 bg-[#00000014] dark:bg-[#26272C]">
<div className="flex items-center gap-2 my-2">
<h4 className="text-lg">{'Cedric medium'}</h4>
<span>{'05:30'}</span>
</div>
<Tab.Group>
<Tab.List className="w-full flex rounded-xl bg-gray-200 dark:bg-[#FFFFFF14] p-2">
{Object.values(ActivityFilters)
.filter((filter) => filter !== 'Tasks')
.map((filter: string) => (
<Tab
key={filter}
className={({ selected }) =>
clsxm(
'w-full rounded-lg py-2.5 text-sm font-medium leading-5',
' focus:outline-none focus:ring-2',
selected
? 'bg-white dark:bg-dark text-blue-700 shadow'
: ' hover:bg-white/[0.50]'
)
}
>
{filter}
</Tab>
))}
</Tab.List>
<Tab.Panels>
<Tab.Panel className="w-full mx-4 py-4">{'Screenshoot Team Tab'}</Tab.Panel>
<Tab.Panel className="w-full mx-4 py-4">{'Apps Tab'}</Tab.Panel>
<Tab.Panel className="w-full mx-4 py-4">{'VisitedSites Tab'}</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</div>
);
};
Loading
Loading