diff --git a/.cspell.json b/.cspell.json
index f35fdc724..140cce74b 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -326,7 +326,8 @@
"Screenshoot",
"screenshoots",
"tota",
- "Intervall"
+ "Intervall",
+ "Timesheets"
],
"useGitignore": true,
"ignorePaths": [
diff --git a/apps/web/app/[locale]/task/[id]/page.tsx b/apps/web/app/[locale]/task/[id]/page.tsx
index b2e0dd908..ac2cf1be3 100644
--- a/apps/web/app/[locale]/task/[id]/page.tsx
+++ b/apps/web/app/[locale]/task/[id]/page.tsx
@@ -85,7 +85,7 @@ const TaskDetails = () => {
{/* */}
{/* */}
-
+ {task && }
diff --git a/apps/web/app/api/timer/timesheet/route.ts b/apps/web/app/api/timer/timesheet/route.ts
index 4ab1a8c61..b014675d5 100644
--- a/apps/web/app/api/timer/timesheet/route.ts
+++ b/apps/web/app/api/timer/timesheet/route.ts
@@ -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);
}
diff --git a/apps/web/app/helpers/array-data.ts b/apps/web/app/helpers/array-data.ts
index ac0e57c77..d96877bf5 100644
--- a/apps/web/app/helpers/array-data.ts
+++ b/apps/web/app/helpers/array-data.ts
@@ -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[] }[] = [];
@@ -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)
diff --git a/apps/web/app/hooks/features/useTaskActivity.ts b/apps/web/app/hooks/features/useTaskActivity.ts
new file mode 100644
index 000000000..0645f8104
--- /dev/null
+++ b/apps/web/app/hooks/features/useTaskActivity.ts
@@ -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
+ };
+}
diff --git a/apps/web/app/interfaces/ITaskTimesheet.ts b/apps/web/app/interfaces/ITaskTimesheet.ts
new file mode 100644
index 000000000..d0e265f94
--- /dev/null
+++ b/apps/web/app/interfaces/ITaskTimesheet.ts
@@ -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'];
+}
diff --git a/apps/web/app/interfaces/index.ts b/apps/web/app/interfaces/index.ts
index 11f63ed70..f6ba725be 100644
--- a/apps/web/app/interfaces/index.ts
+++ b/apps/web/app/interfaces/index.ts
@@ -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';
diff --git a/apps/web/app/services/client/api/activity/index.ts b/apps/web/app/services/client/api/activity/index.ts
new file mode 100644
index 000000000..eacc92516
--- /dev/null
+++ b/apps/web/app/services/client/api/activity/index.ts
@@ -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
(endpoint);
+}
diff --git a/apps/web/app/services/client/api/index.ts b/apps/web/app/services/client/api/index.ts
index 0564893be..7b8f36e10 100644
--- a/apps/web/app/services/client/api/index.ts
+++ b/apps/web/app/services/client/api/index.ts
@@ -32,5 +32,6 @@ export * from './organization-projects';
export * from './activity/time-slots';
export * from './activity/activity';
+export * from './activity';
export * from './default';
export * from './projects';
diff --git a/apps/web/app/services/client/api/organization-team.ts b/apps/web/app/services/client/api/organization-team.ts
index 541cbbc59..3bbe1b66a 100644
--- a/apps/web/app/services/client/api/organization-team.ts
+++ b/apps/web/app/services/client/api/organization-team.ts
@@ -102,7 +102,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()}`;
diff --git a/apps/web/app/services/server/requests/organization-team.ts b/apps/web/app/services/server/requests/organization-team.ts
index e98ed6398..b473e7104 100644
--- a/apps/web/app/services/server/requests/organization-team.ts
+++ b/apps/web/app/services/server/requests/organization-team.ts
@@ -103,7 +103,7 @@ export function getOrganizationTeamRequest(
params[`relations[${i}]`] = rl;
});
- const queries = new URLSearchParams(params || {});
+ const queries = new URLSearchParams(params);
return serverFetch({
path: `/organization-team/${teamId}?${queries.toString()}`,
method: 'GET',
diff --git a/apps/web/app/services/server/requests/timesheet.ts b/apps/web/app/services/server/requests/timesheet.ts
index 7b17116a2..4dfda5ce6 100644
--- a/apps/web/app/services/server/requests/timesheet.ts
+++ b/apps/web/app/services/server/requests/timesheet.ts
@@ -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({
+ path: `/timesheet/activity?${queries.toString()}`,
+ method: 'GET',
+ bearer_token,
+ tenantId: params.tenantId
+ });
+}
diff --git a/apps/web/app/stores/task-timesheet.ts b/apps/web/app/stores/task-timesheet.ts
new file mode 100644
index 000000000..8bf26567c
--- /dev/null
+++ b/apps/web/app/stores/task-timesheet.ts
@@ -0,0 +1,7 @@
+import { ITaskTimesheet } from '@app/interfaces';
+import { atom } from 'recoil';
+
+export const taskTimesheetState = atom({
+ key: 'taskTimesheetState',
+ default: []
+});
diff --git a/apps/web/components/pages/maintenance/index.tsx b/apps/web/components/pages/maintenance/index.tsx
index 86eadab68..a23461fd9 100644
--- a/apps/web/components/pages/maintenance/index.tsx
+++ b/apps/web/components/pages/maintenance/index.tsx
@@ -6,8 +6,8 @@ function Maintenance() {
const t = useTranslations();
return (
-
-
+
+
Maintenance
diff --git a/apps/web/components/pages/task/details-section/blocks/task-estimations-info.tsx b/apps/web/components/pages/task/details-section/blocks/task-estimations-info.tsx
index 7c7be7f80..60a17cec9 100644
--- a/apps/web/components/pages/task/details-section/blocks/task-estimations-info.tsx
+++ b/apps/web/components/pages/task/details-section/blocks/task-estimations-info.tsx
@@ -40,7 +40,7 @@ const TaskEstimationsInfo = () => {
- {task?.members.map((member) => {
+ {task?.members?.map((member) => {
// TODO
// Enable other users estimations in v2
return (
diff --git a/apps/web/lib/features/task/activity/user-task-activity.tsx b/apps/web/lib/features/task/activity/user-task-activity.tsx
new file mode 100644
index 000000000..97cf47f23
--- /dev/null
+++ b/apps/web/lib/features/task/activity/user-task-activity.tsx
@@ -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 (
+
+
+
{'Cedric medium'}
+ {'05:30'}
+
+
+
+ {Object.values(ActivityFilters)
+ .filter((filter) => filter !== 'Tasks')
+ .map((filter: string) => (
+
+ 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}
+
+ ))}
+
+
+ {'Screenshoot Team Tab'}
+ {'Apps Tab'}
+ {'VisitedSites Tab'}
+
+
+
+ );
+};
diff --git a/apps/web/lib/features/task/task-activity.tsx b/apps/web/lib/features/task/task-activity.tsx
index 110df0e89..a325be303 100644
--- a/apps/web/lib/features/task/task-activity.tsx
+++ b/apps/web/lib/features/task/task-activity.tsx
@@ -1,68 +1,54 @@
-import { clsxm } from '@app/utils';
-import { Tab } from '@headlessui/react';
+'use client';
+
import { Card } from 'lib/components';
-import { ActivityFilters } from '@app/constants';
import React from 'react';
+import { UserTaskActivity } from './activity/user-task-activity';
+import { ITeamTask } from '@app/interfaces';
+import { useTaskTimeSheets } from '@app/hooks/features/useTaskActivity';
+import { groupByTime } from '@app/helpers/array-data';
+
+export function TaskActivity({ task }: { task: ITeamTask }) {
+ // get users tasks
+ const { getTaskTimesheets, taskTimesheets } = useTaskTimeSheets(task?.id);
+ // order activity arr by Time
+ const groupedData = groupByTime(taskTimesheets);
+ console.log(groupedData);
-export function TaskActivity() {
+ React.useEffect(() => {
+ getTaskTimesheets();
+ }, [task, getTaskTimesheets]);
return (
+ {/* TO DELETE: start */}
{'05.01.2024'}
+ {/* TO DELETE: end */}
+ {groupedData.map((timesheet, i) => (
+
+
{timesheet.date}
+ {timesheet.items.map((item) => (
+
+ ))}
+
+ ))}
-
-
{'04.01.2024'}
-
-
-
+ {/* TO DELETE: start */}
{'03.01.2024'}
+ {/* TO DELETE: end */}
);
}
-
-const UserTaskActivity = () => {
- return (
-
-
-
{'Cedric medium'}
- {'05:30'}
-
-
-
- {Object.values(ActivityFilters).filter(filter => filter !== 'Tasks').map((filter: string) => (
-
- 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}
-
- ))}
-
-
- {'Screenshoot Team Tab'}
- {'Apps Tab'}
- {'VisitedSites Tab'}
-
-
-
- );
-};