diff --git a/apps/web/app/api/timer/daily/route.ts b/apps/web/app/api/timer/daily/route.ts new file mode 100644 index 000000000..74de89313 --- /dev/null +++ b/apps/web/app/api/timer/daily/route.ts @@ -0,0 +1,29 @@ +import { authenticatedGuard } from '@app/services/server/guards/authenticated-guard-app'; +import { getEmployeeDailyRequest } from '@app/services/server/requests/timer/daily'; +import { NextResponse } from 'next/server'; + +export async function GET(req: Request) { + const res = new NextResponse(); + const { $res, user, tenantId, organizationId, access_token } = await authenticatedGuard(req, res); + if (!user) return $res('Unauthorized'); + + const { searchParams } = new URL(req.url); + + const { endDate, startDate, type } = searchParams as unknown as { + startDate: Date; + endDate: Date; + type: string; + }; + + const { data } = await getEmployeeDailyRequest({ + tenantId, + organizationId, + employeeId: user.employee.id, + todayEnd: endDate, + todayStart: startDate, + type, + bearer_token: access_token + }); + + return $res(data); +} diff --git a/apps/web/app/helpers/array-data.ts b/apps/web/app/helpers/array-data.ts index bb10fe1e7..ac0e57c77 100644 --- a/apps/web/app/helpers/array-data.ts +++ b/apps/web/app/helpers/array-data.ts @@ -1,5 +1,6 @@ import { ITimerSlot } from '@app/interfaces/timer/ITimerSlot'; import { pad } from './number'; +import { ITimerApps } from '@app/interfaces/timer/ITimerApp'; export function groupDataByHour(data: ITimerSlot[]) { const groupedData: { startedAt: string; stoppedAt: string; items: ITimerSlot[] }[] = []; @@ -24,9 +25,31 @@ export function groupDataByHour(data: ITimerSlot[]) { return groupedData.sort((a, b) => (new Date(a.stoppedAt) < new Date(b.stoppedAt) ? 1 : -1)); } +export function groupAppsByHour(apps: ITimerApps[]) { + const groupedData: { hour: string; totalMilliseconds: number; apps: ITimerApps[] }[] = []; + + apps.forEach((app) => { + const time = app.time.slice(0, 5); + + const hourDataIndex = groupedData.findIndex((el) => el.hour == time); + + if (hourDataIndex !== -1) { + groupedData[hourDataIndex].apps.push(app); + groupedData[hourDataIndex].totalMilliseconds += +app.duration; + } else + groupedData.push({ + hour: app.time.slice(0, 5), + totalMilliseconds: +app.duration, + apps: [app] + }); + }); + + return groupedData.sort((a, b) => (new Date(a.hour) > new Date(b.hour) ? -1 : 1)); +} + const formatTime = (d: Date | string, addHour: boolean) => { d = d instanceof Date ? d : new Date(d); if (addHour) - return `${new Date(d).getHours() < 10 ? pad(new Date(d).getHours()) + 1 : new Date(d).getHours() + 1}:00`; + return `${new Date(d).getHours() < 10 ? pad(new Date(d).getHours() + 1) : new Date(d).getHours() + 1}:00`; else return `${new Date(d).getHours() < 10 ? pad(new Date(d).getHours()) : new Date(d).getHours()}:00`; }; diff --git a/apps/web/app/hooks/features/useTimeDailyActivity.ts b/apps/web/app/hooks/features/useTimeDailyActivity.ts new file mode 100644 index 000000000..cd71dd4b2 --- /dev/null +++ b/apps/web/app/hooks/features/useTimeDailyActivity.ts @@ -0,0 +1,47 @@ +'use client'; + +import { useCallback, useEffect } from 'react'; +import { useQuery } from '../useQuery'; +import { useRecoilState } from 'recoil'; +import { timeAppsState } from '@app/stores/time-slot'; +import moment from 'moment'; +import { useAuthenticateUser } from './useAuthenticateUser'; +import { getTimerDailyRequestAPI } from '@app/services/client/api'; + +export function useTimeDailyActivity(type: string) { + const { user } = useAuthenticateUser(); + const [visitedApps, setVisitedApps] = useRecoilState(timeAppsState); + + const { loading, queryCall } = useQuery(getTimerDailyRequestAPI); + + const getVisitedApps = useCallback(() => { + const todayStart = moment().startOf('day').toDate(); + const todayEnd = moment().endOf('day').toDate(); + + queryCall({ + tenantId: user?.tenantId ?? '', + organizationId: user?.employee.organizationId ?? '', + employeeId: user?.employee.id ?? '', + todayEnd, + type, + todayStart + }) + .then((response) => { + if (response.data) { + console.log(response.data); + setVisitedApps(response.data?.data); + } + }) + .catch((err) => console.log(err)); + }, [queryCall, setVisitedApps, user, type]); + + useEffect(() => { + getVisitedApps(); + }, [user, getVisitedApps]); + + return { + visitedApps, + getVisitedApps, + loading + }; +} diff --git a/apps/web/app/interfaces/timer/ITimerApp.ts b/apps/web/app/interfaces/timer/ITimerApp.ts new file mode 100644 index 000000000..c061d5134 --- /dev/null +++ b/apps/web/app/interfaces/timer/ITimerApp.ts @@ -0,0 +1,8 @@ +export interface ITimerApps { + sessions: string; + duration: string; + employeeId: string; + date: Date | string; + time: string; + title: string; +} diff --git a/apps/web/app/services/client/api/activity/activity.ts b/apps/web/app/services/client/api/activity/activity.ts new file mode 100644 index 000000000..358968aa9 --- /dev/null +++ b/apps/web/app/services/client/api/activity/activity.ts @@ -0,0 +1,35 @@ +import { get } from '@app/services/client/axios'; +import { GAUZY_API_BASE_SERVER_URL } from '@app/constants'; + +export async function getTimerDailyRequestAPI({ + tenantId, + organizationId, + employeeId, + todayEnd, + todayStart, + type +}: { + tenantId: string; + organizationId: string; + employeeId: string; + todayEnd: Date; + todayStart: Date; + type: string; +}) { + const params = { + tenantId: tenantId, + organizationId: organizationId, + 'employeeIds[0]': employeeId, + startDate: todayStart.toISOString(), + endDate: todayEnd.toISOString(), + 'types[0]': type + }; + const query = new URLSearchParams(params); + const endpoint = GAUZY_API_BASE_SERVER_URL.value + ? `/timesheet/activity/daily?${query.toString()}` + : `/timer/daily?${query.toString()}`; + + const data = await get(endpoint, true); + + return data; +} diff --git a/apps/web/app/services/client/api/index.ts b/apps/web/app/services/client/api/index.ts index 2d2910aa7..29584d0ee 100644 --- a/apps/web/app/services/client/api/index.ts +++ b/apps/web/app/services/client/api/index.ts @@ -31,3 +31,4 @@ export * from './integrations'; export * from './organization-projects'; export * from './activity/time-slots'; +export * from './activity/activity'; diff --git a/apps/web/app/services/server/requests/timer/daily.ts b/apps/web/app/services/server/requests/timer/daily.ts new file mode 100644 index 000000000..dcecaee3a --- /dev/null +++ b/apps/web/app/services/server/requests/timer/daily.ts @@ -0,0 +1,36 @@ +import { serverFetch } from '../../fetch'; +import { ITimerSlotDataRequest } from '@app/interfaces/timer/ITimerSlot'; + +export function getEmployeeDailyRequest({ + bearer_token, + tenantId, + organizationId, + todayEnd, + todayStart, + employeeId, + type +}: { + bearer_token: string; + tenantId: string; + organizationId: string; + todayEnd: Date; + todayStart: Date; + employeeId: string; + type: string; +}) { + const params = { + tenantId: tenantId, + organizationId: organizationId, + 'employeeIds[0]': employeeId, + startDate: todayStart.toISOString(), + endDate: todayEnd.toISOString(), + 'types[0]': type + }; + const query = new URLSearchParams(params); + return serverFetch({ + path: `/timesheet/activity/daily?${query.toString()}`, + method: 'GET', + bearer_token, + tenantId + }); +} diff --git a/apps/web/app/stores/time-slot.ts b/apps/web/app/stores/time-slot.ts index cf933eec3..59baccc94 100644 --- a/apps/web/app/stores/time-slot.ts +++ b/apps/web/app/stores/time-slot.ts @@ -1,3 +1,4 @@ +import { ITimerApps } from '@app/interfaces/timer/ITimerApp'; import { ITimerSlot } from '@app/interfaces/timer/ITimerSlot'; import { atom } from 'recoil'; @@ -5,3 +6,8 @@ export const timeSlotsState = atom({ key: 'timeSlotsState', default: [] }); + +export const timeAppsState = atom({ + key: 'timeAppsState', + default: [] +}); diff --git a/apps/web/lib/features/activity/apps.tsx b/apps/web/lib/features/activity/apps.tsx index f4e2755a1..de8620e2c 100644 --- a/apps/web/lib/features/activity/apps.tsx +++ b/apps/web/lib/features/activity/apps.tsx @@ -1,3 +1,50 @@ +import { useTimeDailyActivity } from '@app/hooks/features/useTimeDailyActivity'; +import { AppVisitedSkeleton } from './components/app-visited-skeleton'; +import { groupAppsByHour } from '@app/helpers/array-data'; +import { useTranslations } from 'next-intl'; +import AppVisitedItem from './components/app-visited-Item'; + export function AppsTab() { - return
Apps Tab
; + const { visitedApps, loading } = useTimeDailyActivity('APP'); + const t = useTranslations(); + const apps = groupAppsByHour(visitedApps); + return ( +
+
{/* TODO: Filters components */}
+
+

{t('timer.APPS')}

+

{t('timer.VISITED_DATES')}

+

{t('timer.PERCENT_USED')}

+

{t('timer.TIME_SPENT_IN_HOURS')}

+
+
+ {apps.map((app, i) => ( +
+

{app.hour}

+
+ {app.apps?.map((item, i) => ( +
+ +
+ ))} +
+
+ ))} +
+ {visitedApps.length < 1 && !loading && ( +
+

{t('timer.THERE_IS_NO_APPS_VISITED')}

+
+ )} + {loading && visitedApps.length < 1 && ( + <> + + + + )} +
+ ); } diff --git a/apps/web/lib/features/activity/components/app-visited-Item.tsx b/apps/web/lib/features/activity/components/app-visited-Item.tsx new file mode 100644 index 000000000..7497ca238 --- /dev/null +++ b/apps/web/lib/features/activity/components/app-visited-Item.tsx @@ -0,0 +1,24 @@ +import { formatDateString, secondsToTime } from '@app/helpers'; +import { ITimerApps } from '@app/interfaces/timer/ITimerApp'; +import { ProgressBar } from 'lib/components'; +import React from 'react'; + +const AppVisitedItem = ({ app, totalMilliseconds }: { app: ITimerApps; totalMilliseconds: number }) => { + const { h, m, s } = secondsToTime(+app.duration); + const percent = ((+app.duration * 100) / totalMilliseconds).toFixed(2); + return ( +
+

{app.title}

+

+ {formatDateString(new Date(app.date).toISOString())} - {app.time} +

+
+

{percent}%

+ +
+

{`${h}:${m}:${s}`}

+
+ ); +}; + +export default AppVisitedItem; diff --git a/apps/web/lib/features/activity/components/app-visited-skeleton.tsx b/apps/web/lib/features/activity/components/app-visited-skeleton.tsx new file mode 100644 index 000000000..02eb9fbbc --- /dev/null +++ b/apps/web/lib/features/activity/components/app-visited-skeleton.tsx @@ -0,0 +1,18 @@ +export function AppVisitedSkeleton() { + return ( +
+
+

+
+
+

+
+
+

+
+
+

+
+
+ ); +} diff --git a/apps/web/lib/features/activity/components/screenshoots-per-hour.tsx b/apps/web/lib/features/activity/components/screenshoots-per-hour.tsx index 853470d0a..39f557c9f 100644 --- a/apps/web/lib/features/activity/components/screenshoots-per-hour.tsx +++ b/apps/web/lib/features/activity/components/screenshoots-per-hour.tsx @@ -26,7 +26,7 @@ export const ScreenshootPerHour = ({ endTime={el.stoppedAt} startTime={el.startedAt} percent={el.percentage} - imageUrl={el.screenshots[0].thumbUrl} + imageUrl={el.screenshots[0]?.thumbUrl} /> ))} diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index a886b9897..cef95badd 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -445,7 +445,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/messages/fr.json b/apps/web/messages/fr.json index 3faf7cf05..0151da1d5 100644 --- a/apps/web/messages/fr.json +++ b/apps/web/messages/fr.json @@ -437,7 +437,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { "TITLE": "Tâche", diff --git a/apps/web/messages/it.json b/apps/web/messages/it.json index c72c75895..2bfd5a3db 100644 --- a/apps/web/messages/it.json +++ b/apps/web/messages/it.json @@ -441,7 +441,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/messages/pl.json b/apps/web/messages/pl.json index 34bea12fd..0855a4b62 100644 --- a/apps/web/messages/pl.json +++ b/apps/web/messages/pl.json @@ -441,7 +441,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/messages/pt.json b/apps/web/messages/pt.json index 90945bfdd..79e1c4bf1 100644 --- a/apps/web/messages/pt.json +++ b/apps/web/messages/pt.json @@ -441,7 +441,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/messages/ru.json b/apps/web/messages/ru.json index ee6db8058..6c04f01f4 100644 --- a/apps/web/messages/ru.json +++ b/apps/web/messages/ru.json @@ -441,7 +441,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/public/locales/en/common.json b/apps/web/public/locales/en/common.json index 28036e5c8..e706d2686 100644 --- a/apps/web/public/locales/en/common.json +++ b/apps/web/public/locales/en/common.json @@ -445,7 +445,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/public/locales/fr/common.json b/apps/web/public/locales/fr/common.json index 9320762e7..95d1f4cf1 100644 --- a/apps/web/public/locales/fr/common.json +++ b/apps/web/public/locales/fr/common.json @@ -437,7 +437,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { "TITLE": "Tâche", diff --git a/apps/web/public/locales/it/common.json b/apps/web/public/locales/it/common.json index 89917f812..8b389d97a 100644 --- a/apps/web/public/locales/it/common.json +++ b/apps/web/public/locales/it/common.json @@ -441,7 +441,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/public/locales/pl/common.json b/apps/web/public/locales/pl/common.json index 9a45df6bc..56be78289 100644 --- a/apps/web/public/locales/pl/common.json +++ b/apps/web/public/locales/pl/common.json @@ -441,7 +441,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/public/locales/pt/common.json b/apps/web/public/locales/pt/common.json index a7739c97d..fde537588 100644 --- a/apps/web/public/locales/pt/common.json +++ b/apps/web/public/locales/pt/common.json @@ -441,7 +441,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": { diff --git a/apps/web/public/locales/ru/common.json b/apps/web/public/locales/ru/common.json index 815a77259..681454fa1 100644 --- a/apps/web/public/locales/ru/common.json +++ b/apps/web/public/locales/ru/common.json @@ -440,7 +440,12 @@ "TIME_ACTIVITY": "Activity", "TOTAL_HOURS": "Total Hours", "NO_SCREENSHOOT": "No Screenshoots", - "PERCENT_OF_MINUTES": " % of 10 Minutes" + "PERCENT_OF_MINUTES": " % of 10 Minutes", + "APPS": "Apps", + "VISITED_DATES": "Visited Dates", + "PERCENT_USED": "Percent Used", + "TIME_SPENT_IN_HOURS": "Time spent (Hours)", + "THERE_IS_NO_APPS_VISITED": "There is no Apps Visited." }, "task": {