Skip to content

Commit

Permalink
Merge pull request #2082 from ever-co/feat/activity
Browse files Browse the repository at this point in the history
Feat: display details of time slots & create API route to delete time slots
  • Loading branch information
evereq authored Jan 11, 2024
2 parents 9e89ffa + 4fe806a commit 005a1ce
Show file tree
Hide file tree
Showing 21 changed files with 1,008 additions and 79 deletions.
33 changes: 33 additions & 0 deletions .nx/cache/nx-console-project-graph/project-graph.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en" class="h-full w-full overflow-hidden">
<head>
<meta charset="utf-8" />
<title>Nx Workspace Project Graph</title>


<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />

<script id="environment" src="static/environment.js"></script>

<!-- Theming -->
<script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC

Check warning on line 15 in .nx/cache/nx-console-project-graph/project-graph.html

View workflow job for this annotation

GitHub Actions / Cspell

Unknown word (FOUC)
if (
localStorage.getItem('nx-dep-graph-theme') === 'dark' ||
(localStorage.getItem('nx-dep-graph-theme') === null &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
<link rel="stylesheet" href="static/styles.css"></head>

<body
class="h-full w-full overflow-hidden bg-white text-slate-500 dark:bg-slate-900 dark:text-slate-400"
>
<div class="flex h-full w-full overflow-hidden p-0" id="app"></div>
<script src="static/runtime.js" ></script><script src="static/polyfills.js" ></script><script src="static/styles.js" ></script><script src="static/main.js" ></script></body>
</html>
665 changes: 665 additions & 0 deletions .nx/cache/nx-console-project-graph/static/3rdpartylicenses.txt

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions .nx/cache/nx-console-project-graph/static/environment.js

Large diffs are not rendered by default.

Binary file not shown.
1 change: 1 addition & 0 deletions .nx/cache/nx-console-project-graph/static/main.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .nx/cache/nx-console-project-graph/static/polyfills.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .nx/cache/nx-console-project-graph/static/runtime.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .nx/cache/nx-console-project-graph/static/styles.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .nx/cache/nx-console-project-graph/static/styles.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion apps/web/app/api/timer/slot/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { authenticatedGuard } from '@app/services/server/guards/authenticated-guard-app';
import { getEmployeeTimeSlotsRequest } from '@app/services/server/requests/timer/timer-slot';
import {
deleteEmployeeTimeSlotsRequest,
getEmployeeTimeSlotsRequest
} from '@app/services/server/requests/timer/timer-slot';
import { NextResponse } from 'next/server';

export async function GET(req: Request) {
Expand Down Expand Up @@ -27,3 +30,21 @@ export async function GET(req: Request) {

return $res(data);
}

export async function DELETE(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 ids: string[] = searchParams.getAll('ids');

const { data } = await deleteEmployeeTimeSlotsRequest({
tenantId,
organizationId,
ids,
bearer_token: access_token
});

return $res(data);
}
20 changes: 18 additions & 2 deletions apps/web/app/hooks/features/useTimeSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { useRecoilState } from 'recoil';
import { timeSlotsState } from '@app/stores/time-slot';
import moment from 'moment';
import { useAuthenticateUser } from './useAuthenticateUser';
import { getTimerLogsRequestAPI } from '@app/services/client/api';
import { deleteTimerLogsRequestAPI, getTimerLogsRequestAPI } from '@app/services/client/api';

export function useTimeSlots() {
export function useTimeSlots(ids?: string[]) {
const { user } = useAuthenticateUser();
const [timeSlots, setTimeSlots] = useRecoilState(timeSlotsState);

const { loading, queryCall } = useQuery(getTimerLogsRequestAPI);
const { loading: loadingDelete, queryCall: queryDeleteCall } = useQuery(deleteTimerLogsRequestAPI);

const getTimeSlots = useCallback(() => {
const todayStart = moment().startOf('day').toDate();
Expand All @@ -30,13 +31,28 @@ export function useTimeSlots() {
});
}, [queryCall, setTimeSlots, user]);

const deleteTimeSlots = useCallback(() => {
if(ids?.length){
queryDeleteCall({
tenantId: user?.tenantId ?? '',
organizationId: user?.employee.organizationId ?? '',
ids: ids
}).then(() => {
const updatedSlots = timeSlots.filter((el) => (!ids?.includes(el.id) ? el : null));
setTimeSlots(updatedSlots);
});
}
}, [queryDeleteCall, setTimeSlots,ids, timeSlots, user]);

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

return {
timeSlots,
getTimeSlots,
deleteTimeSlots,
loadingDelete,
loading
};
}
2 changes: 2 additions & 0 deletions apps/web/app/interfaces/IScreenshoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export interface IScreenShootItem {
endTime: Date | string;
imageUrl: string;
percent: number | string;
showProgress?: boolean;
onShow: () => any;
}
42 changes: 22 additions & 20 deletions apps/web/app/interfaces/timer/ITimerSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,28 @@ export interface ITimerSlot {
keyboardPercentage: number;
mousePercentage: number;

screenshots: {
organizationId: string;
id: string;
createdAt: Date | string;
updatedAt: Date | string;
deletedAt: null;
isActive: true;
isArchived: false;
tenantId: string;
file: string;
thumb: string;
thumbUrl: string;
recordedAt: Date | string;
isWorkRelated: true;
description: string;
timeSlotId: string;
userId: string;
fullUrl: string;
apps: string[];
}[];
screenshots: IScreenshot[];
}

export interface IScreenshot {
organizationId: string;
id: string;
createdAt: Date | string;
updatedAt: Date | string;
deletedAt: null;
isActive: true;
isArchived: false;
tenantId: string;
file: string;
thumb: string;
thumbUrl: string;
recordedAt: Date | string;
isWorkRelated: true;
description: string;
timeSlotId: string;
userId: string;
fullUrl: string;
apps: string[];
}

export interface ITimerSlotDataRequest {
Expand Down
27 changes: 27 additions & 0 deletions apps/web/app/services/client/api/activity/time-slots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,30 @@ export async function getTimerLogsRequestAPI({

return data;
}

export async function deleteTimerLogsRequestAPI({
tenantId,
organizationId,
ids
}: {
tenantId: string;
organizationId: string;
ids: string[];
}) {
let idParams = '';
ids.map((id, i) => {
idParams += `&ids[${i}]=${id}`;
});
const params = {
tenantId: tenantId,
organizationId: organizationId
};
const query = new URLSearchParams(params);
const endpoint = GAUZY_API_BASE_SERVER_URL.value
? `/timesheet/statistics/time-slots?${query.toString()}${idParams}`
: `/timer/slots?${query.toString()}${idParams}`;

const data = await get(endpoint, true);

return data;
}
30 changes: 30 additions & 0 deletions apps/web/app/services/server/requests/timer/timer-slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,33 @@ export function getEmployeeTimeSlotsRequest({
tenantId
});
}

export function deleteEmployeeTimeSlotsRequest({
bearer_token,
tenantId,
organizationId,
ids
}: {
bearer_token: string;
tenantId: string;
organizationId: string;
ids: string[];
}) {
let idParams = '';
ids.map((id, i) => {
idParams += `&ids[${i}]=${id}`;
});
const params = {
tenantId: tenantId,
organizationId: organizationId
};
const query = new URLSearchParams(params);
return serverFetch<ITimerSlotDataRequest>({
path: `/timesheet/statistics/time-slots?${query.toString()}${idParams}`,
method: 'GET',
bearer_token,
tenantId
});
}

// https://apidemo.gauzy.co/api/timesheet/time-slot?ids[0]=71bde97d-f6e7-463a-90ef-c752072755ab&organizationId=0289f323-5aa5-4dc2-92c5-633f3e70ecb4&tenantId=ae69bf6c-072f-44cb-8f41-e981a3eccdb1
10 changes: 5 additions & 5 deletions apps/web/lib/features/activity/apps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ export function AppsTab() {
<div>
<div className="flex justify-end w-full">{/* TODO: Filters components */}</div>
<header className="bg-gray-200 dark:bg-[#26272C] rounded-md p-4 flex items-center justify-between">
<h3 className="text-lg font-semibold w-1/4">{t('timer.APPS')}</h3>
<h3 className="text-lg text-center font-semibold w-1/4">{t('timer.VISITED_DATES')}</h3>
<h3 className="text-lg text-center font-semibold w-1/4">{t('timer.PERCENT_USED')}</h3>
<h3 className="text-lg font-semibold w-1/4 text-end">{t('timer.TIME_SPENT_IN_HOURS')}</h3>
<h3 className="text-lg font-semibold flex-1">{t('timer.APPS')}</h3>
<h3 className="text-lg text-center font-semibold 2xl:w-56 3xl:w-64">{t('timer.VISITED_DATES')}</h3>
<h3 className="text-lg text-center font-semibold flex-1">{t('timer.PERCENT_USED')}</h3>
<h3 className="text-lg font-semibold 2xl:w-52 3xl:w-64 text-end">{t('timer.TIME_SPENT_IN_HOURS')}</h3>
</header>
<section>
{apps.map((app, i) => (
<div
key={i}
className="border shadow-lg shadow-gray-200 rounded-md my-4 p-4 dark:border-[#FFFFFF0D] bg-white dark:bg-[#1B1D22]"
className="border shadow-lg rounded-md my-4 p-4 dark:border-[#FFFFFF0D] bg-white dark:bg-[#1B1D22]"
>
<h3>{app.hour}</h3>
<div>
Expand Down
10 changes: 5 additions & 5 deletions apps/web/lib/features/activity/components/app-visited-Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ const AppVisitedItem = ({ app, totalMilliseconds }: { app: ITimerApps; totalMill
const percent = ((+app.duration * 100) / totalMilliseconds).toFixed(2);
return (
<div className="hover:dark:bg-[#26272C] border dark:border-[#26272C] bg-gray-200 dark:bg-[#191a20] p-4 rounded-md flex justify-between apps-center my-2">
<p className="text-lg w-1/4">{app.title}</p>
<p className="text-lg text-center w-1/4">
<p className="text-lg flex-1">{app.title}</p>
<p className="text-lg text-center 2xl:w-56 3xl:w-64">
{formatDateString(new Date(app.date).toISOString())} - {app.time}
</p>
<div className="text-lg w-1/4 flex justify-center gap-2 px-4">
<p className="w-1/4">{percent}%</p>
<div className="text-lg flex-1 flex justify-center gap-2 px-4">
<p className="w-1/4 text-end">{percent}%</p>
<ProgressBar progress={percent + '%'} width={`75%`} />
</div>
<p className="text-lg w-1/4 text-end">{`${h}:${m}:${s}`}</p>
<p className="text-lg 2xl:w-52 3xl:w-64 text-end">{`${h}:${m}:${s}`}</p>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { IScreenShootItem } from '@app/interfaces/IScreenshoot';
import { ITimerSlot } from '@app/interfaces/timer/ITimerSlot';
import { clsxm } from '@app/utils';
import { ProgressBar } from 'lib/components';
import Image from 'next/image';
import { useTranslations } from 'next-intl';
import ScreenshotDetailsModal from './screenshot-details';
import { useModal } from '@app/hooks';
import ScreenshotItem from './screenshot-item';

export const ScreenshootPerHour = ({
timeSlots,
Expand All @@ -14,6 +13,7 @@ export const ScreenshootPerHour = ({
startedAt: string;
stoppedAt: string;
}) => {
const { isOpen, closeModal, openModal } = useModal();
return (
<div className="p-4 my-4 rounded-md dark:bg-[#1E2025] border-[0.125rem] dark:border-[#FFFFFF0D]">
<h3 className="px-4">
Expand All @@ -22,53 +22,17 @@ export const ScreenshootPerHour = ({
<div className="flex justify-start items-start flex-wrap ">
{timeSlots.map((el, i) => (
<div key={i} className={clsxm('min-w-[20rem] xl:w-1/6 p-4')}>
<ScreenShootItem
<ScreenshotItem
endTime={el.stoppedAt}
startTime={el.startedAt}
percent={el.percentage}
imageUrl={el.screenshots[0]?.thumbUrl}
onShow={() => openModal()}
/>
<ScreenshotDetailsModal open={isOpen} closeModal={closeModal} slot={el} />
</div>
))}
</div>
</div>
);
};

const ScreenShootItem = ({ endTime, imageUrl, percent, startTime }: IScreenShootItem) => {
const t = useTranslations();
return (
<div
className={clsxm(
'rounded-lg shadow-md hover:shadow-lg cursor-pointer border dark:border-[#26272C] dark:bg-[#191a20] overflow-hidden h-56 w-full'
)}
>
<div className="w-full h-1/2 object-cover bg-gray-200 dark:bg-[#26272C] relative">
<Image
src={imageUrl}
alt={`screenshoot-${imageUrl}`}
width={400}
height={400}
className="w-full h-full"
/>
</div>
<div className="w-full h-1/2 p-4">
<h4 className="font-semibold text-xs">
{new Date(startTime).toLocaleTimeString()} - {new Date(endTime).toLocaleTimeString()}
</h4>
<p className="text-xs mb-6">
{new Date(startTime).toLocaleDateString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric'
})}
</p>
<ProgressBar width={'100%'} progress={`${percent}%`} className="my-2 w-full" />
<p className="font-semibold text-sm">
{percent} {t('timer.PERCENT_OF_MINUTES')}
</p>
</div>
</div>
);
};
Loading

0 comments on commit 005a1ce

Please sign in to comment.