Skip to content

Commit

Permalink
[Refactor]: Optimize DeleteTimesheet and UpdateTimesheetStatus (#3375)
Browse files Browse the repository at this point in the history
* Add createTimesheetFromApi function to create a timesheet entry from API

* fix:update url

* refactor: optimize deleteTimesheet and updateTimesheetStatus function for better readability

* fix: deep scan

* fix

* fix: refactor some code

* fix: deep scan

* fix: Codacy Static Code Analysis

* fix: build

* feat: update billable from api

* fix: conflits

* fix: coderabbitai
  • Loading branch information
Innocent-Akim authored Nov 27, 2024
1 parent 8a47843 commit 4eccf23
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 61 deletions.
33 changes: 33 additions & 0 deletions apps/web/app/api/timer/timesheet/time-log/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { UpdateTimesheet } from "@/app/interfaces";
import { authenticatedGuard } from "@/app/services/server/guards/authenticated-guard-app";
import { updateTimesheetRequest } from "@/app/services/server/requests";
import { NextResponse } from "next/server";

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

try {
const { searchParams } = new URL(req.url);
const id = searchParams.get('id') as string;
const body = (await req.json()) as UpdateTimesheet;
const { data } = await updateTimesheetRequest(
{ ...body, tenantId, organizationId, id },
access_token
);
return $res(data);
} catch (error) {
console.error('Error updating timesheet status:', error);
return $res({
success: false,
message: 'Failed to update timesheet status'
});
}
}
31 changes: 31 additions & 0 deletions apps/web/app/api/timer/timesheet/time-log/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { UpdateTimesheet } from "@/app/interfaces";
import { authenticatedGuard } from "@/app/services/server/guards/authenticated-guard-app";
import { createTimesheetRequest } from "@/app/services/server/requests";
import { NextResponse } from "next/server";

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

try {
const body = (await req.json()) as UpdateTimesheet;
const { data } = await createTimesheetRequest(
{ ...body, tenantId, organizationId },
access_token
);
return $res(data);
} catch (error) {
console.error('Error updating timesheet status:', error);
return $res({
success: false,
message: 'Failed to update timesheet status'
});
}
}
116 changes: 78 additions & 38 deletions apps/web/app/hooks/features/useTimesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useAtom } from 'jotai';
import { timesheetRapportState } from '@/app/stores/time-logs';
import { useQuery } from '../useQuery';
import { useCallback, useEffect, useMemo } from 'react';
import { deleteTaskTimesheetLogsApi, getTaskTimesheetLogsApi, updateStatusTimesheetFromApi } from '@/app/services/client/api/timer/timer-log';
import { deleteTaskTimesheetLogsApi, getTaskTimesheetLogsApi, updateStatusTimesheetFromApi, createTimesheetFromApi, updateTimesheetFromAPi } from '@/app/services/client/api/timer/timer-log';
import moment from 'moment';
import { ID, TimesheetLog, TimesheetStatus } from '@/app/interfaces';
import { ID, TimesheetLog, TimesheetStatus, UpdateTimesheet } from '@/app/interfaces';
import { useTimelogFilterOptions } from './useTimelogFilterOptions';

interface TimesheetParams {
Expand All @@ -19,13 +19,6 @@ export interface GroupedTimesheet {
}


interface DeleteTimesheetParams {
organizationId: string;
tenantId: string;
logIds: string[];
}


const groupByDate = (items: TimesheetLog[]): GroupedTimesheet[] => {
if (!items?.length) return [];
type GroupedMap = Record<string, TimesheetLog[]>;
Expand Down Expand Up @@ -104,6 +97,8 @@ export function useTimesheet({
const { loading: loadingTimesheet, queryCall: queryTimesheet } = useQuery(getTaskTimesheetLogsApi);
const { loading: loadingDeleteTimesheet, queryCall: queryDeleteTimesheet } = useQuery(deleteTaskTimesheetLogsApi);
const { loading: loadingUpdateTimesheetStatus, queryCall: queryUpdateTimesheetStatus } = useQuery(updateStatusTimesheetFromApi)
const { loading: loadingCreateTimesheet, queryCall: queryCreateTimesheet } = useQuery(createTimesheetFromApi);
const { loading: loadingUpdateTimesheet, queryCall: queryUpdateTimesheet } = useQuery(updateTimesheetFromAPi);


const getTaskTimesheet = useCallback(
Expand Down Expand Up @@ -138,30 +133,76 @@ export function useTimesheet({
]
);

const createTimesheet = useCallback(
async ({ ...timesheetParams }: UpdateTimesheet) => {
if (!user) {
throw new Error("User not authenticated");
}
try {
const response = await queryCreateTimesheet(timesheetParams);
setTimesheet((prevTimesheet) => [
response.data,
...(prevTimesheet || [])
]);
} catch (error) {
console.error('Error:', error);
}
},
[queryCreateTimesheet, setTimesheet, user]
);



const updateTimesheet = useCallback<(params: UpdateTimesheet) => Promise<void>>(
async ({ ...timesheet }: UpdateTimesheet) => {
if (!user) {
throw new Error("User not authenticated");
}
try {
const response = await queryUpdateTimesheet(timesheet);
setTimesheet(prevTimesheet =>
prevTimesheet.map(item =>
item.timesheet.id === response.data.timesheet.id
? response.data
: item
)
);
} catch (error) {
console.error('Error updating timesheet:', error);
throw error;
}
}, [queryUpdateTimesheet, setTimesheet, user])


const updateTimesheetStatus = useCallback(
({ status, ids }: { status: TimesheetStatus, ids: ID[] | ID }) => {
async ({ status, ids }: { status: TimesheetStatus; ids: ID[] | ID }) => {
if (!user) return;
queryUpdateTimesheetStatus({ ids, status })
.then((response) => {
const updatedData = timesheet.map(item => {
const newItem = response.data.find(newItem => newItem.id === item.timesheet.id);
if (newItem) {
const idsArray = Array.isArray(ids) ? ids : [ids];
try {
const response = await queryUpdateTimesheetStatus({ ids: idsArray, status });
const responseMap = new Map(response.data.map(item => [item.id, item]));
setTimesheet(prevTimesheet =>
prevTimesheet.map(item => {
const updatedItem = responseMap.get(item.timesheet.id);
if (updatedItem) {
return {
...item,
timesheet: {
...item.timesheet,
status: newItem.status
status: updatedItem.status
}
};
}
return item;
});
setTimesheet(updatedData);
})
.catch((error) => {
console.error('Error fetching timesheet:', error);
});
}, [queryUpdateTimesheetStatus])
})
);
console.log('Timesheet status updated successfully!');
} catch (error) {
console.error('Error updating timesheet status:', error);
}
},
[queryUpdateTimesheetStatus, setTimesheet, user]
);

const getStatusTimesheet = (items: TimesheetLog[] = []) => {
const STATUS_MAP: Record<TimesheetStatus, TimesheetLog[]> = {
Expand Down Expand Up @@ -196,15 +237,6 @@ export function useTimesheet({
}


const handleDeleteTimesheet = async (params: DeleteTimesheetParams) => {
try {
return await queryDeleteTimesheet(params);
} catch (error) {
console.error('Error deleting timesheet:', error);
throw error;
}
};

const deleteTaskTimesheet = useCallback(async () => {
if (!user) {
throw new Error('User not authenticated');
Expand All @@ -213,18 +245,22 @@ export function useTimesheet({
throw new Error('No timesheet IDs provided for deletion');
}
try {
await handleDeleteTimesheet({
await queryDeleteTimesheet({
organizationId: user.employee.organizationId,
tenantId: user.tenantId ?? "",
logIds
});
setTimesheet(prevTimesheet =>
prevTimesheet.filter(item => !logIds.includes(item.timesheet.id))
);

} catch (error) {
console.error('Failed to delete timesheets:', error);
throw error;
}
},
[user, queryDeleteTimesheet, logIds, handleDeleteTimesheet] // deepscan-disable-line
);
}, [user, logIds, queryDeleteTimesheet, setTimesheet]);


const timesheetElementGroup = useMemo(() => {
if (timesheetGroupByDays === 'Daily') {
return groupByDate(timesheet);
Expand All @@ -238,7 +274,7 @@ export function useTimesheet({

useEffect(() => {
getTaskTimesheet({ startDate, endDate });
}, [getTaskTimesheet, startDate, endDate, timesheetGroupByDays, timesheet]);
}, [getTaskTimesheet, startDate, endDate, timesheetGroupByDays]);

return {
loadingTimesheet,
Expand All @@ -251,6 +287,10 @@ export function useTimesheet({
statusTimesheet: getStatusTimesheet(timesheet.flat()),
updateTimesheetStatus,
loadingUpdateTimesheetStatus,
puTimesheetStatus
puTimesheetStatus,
createTimesheet,
loadingCreateTimesheet,
updateTimesheet,
loadingUpdateTimesheet
};
}
34 changes: 27 additions & 7 deletions apps/web/app/interfaces/timer/ITimerLog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ITeamTask } from "../ITask";
import { ITeamTask, TimesheetStatus } from "../ITask";
import { TimeLogType, TimerSource } from "../ITimer";

interface BaseEntity {
id: string;
Expand Down Expand Up @@ -69,7 +70,7 @@ interface Timesheet extends BaseEntity {
lockedAt: string | null;
editedAt: string | null;
isBilled: boolean;
status: string;
status: TimesheetStatus;
employeeId: string;
approvedById: string | null;
isEdited: boolean;
Expand All @@ -87,8 +88,8 @@ export interface TimesheetLog extends BaseEntity {
startedAt: string;
stoppedAt: string;
editedAt: string | null;
logType: "TRACKED" | "MANUAL";
source: "WEB_TIMER" | "MOBILE_APP" | "DESKTOP_APP";
logType: TimeLogType;
source: TimerSource;
description: string;
reason: string | null;
isBillable: boolean;
Expand All @@ -112,9 +113,6 @@ export interface TimesheetLog extends BaseEntity {


export interface UpdateTimesheetStatus extends BaseEntity {
isActive: boolean;
isArchived: boolean;
archivedAt: string | null;
duration: number;
keyboard: number;
mouse: number;
Expand All @@ -137,3 +135,25 @@ export interface UpdateTimesheetStatus extends BaseEntity {
employee: Employee;
isEdited: boolean;
}
export interface UpdateTimesheet extends Pick<
Partial<TimesheetLog>,
| 'reason'
| 'organizationContactId'
| 'description'
| 'organizationTeamId'
| 'projectId'
| 'taskId'
>,
Pick<
TimesheetLog,
| 'id'
| 'startedAt'
| 'stoppedAt'
| 'tenantId'
| 'logType'
| 'source'
| 'employeeId'
| 'organizationId'
> {
isBillable: boolean;
}
32 changes: 30 additions & 2 deletions apps/web/app/services/client/api/timer/timer-log.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TimesheetLog, ITimerStatus, IUpdateTimesheetStatus, UpdateTimesheetStatus } from '@app/interfaces';
import { get, deleteApi, put } from '../../axios';
import { TimesheetLog, ITimerStatus, IUpdateTimesheetStatus, UpdateTimesheetStatus, UpdateTimesheet } from '@app/interfaces';
import { get, deleteApi, put, post } from '../../axios';
import { getOrganizationIdCookie, getTenantIdCookie } from '@/app/helpers';

export async function getTimerLogs(
Expand Down Expand Up @@ -127,3 +127,31 @@ export function updateStatusTimesheetFromApi(data: IUpdateTimesheetStatus) {
const tenantId = getTenantIdCookie();
return put<UpdateTimesheetStatus[]>(`/timesheet/status`, { ...data, organizationId }, { tenantId });
}


export function createTimesheetFromApi(data: UpdateTimesheet) {
const organizationId = getOrganizationIdCookie();
const tenantId = getTenantIdCookie();
if (!organizationId || !tenantId) {
throw new Error('Required parameters missing: organizationId and tenantId are required');
}
try {
return post<TimesheetLog>('/timesheet/time-log', { ...data, organizationId }, { tenantId })
} catch (error) {
throw new Error('Failed to create timesheet log');
}
}

export function updateTimesheetFromAPi(params: UpdateTimesheet) {
const { id, ...data } = params
const organizationId = getOrganizationIdCookie();
const tenantId = getTenantIdCookie();
if (!organizationId || !tenantId) {
throw new Error('Required parameters missing: organizationId and tenantId are required');
}
try {
return put<TimesheetLog>(`/timesheet/time-log/${params.id}`, { ...data, organizationId }, { tenantId })
} catch (error) {
throw new Error('Failed to create timesheet log');
}
}
23 changes: 22 additions & 1 deletion apps/web/app/services/server/requests/timesheet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ITasksTimesheet } from '@app/interfaces/ITimer';
import { serverFetch } from '../fetch';
import qs from 'qs';
import { TimesheetLog, UpdateTimesheetStatus } from '@/app/interfaces/timer/ITimerLog';
import { TimesheetLog, UpdateTimesheet, UpdateTimesheetStatus } from '@/app/interfaces/timer/ITimerLog';
import { IUpdateTimesheetStatus } from '@/app/interfaces';

export type TTasksTimesheetStatisticsParams = {
Expand Down Expand Up @@ -107,3 +107,24 @@ export function updateStatusTimesheetRequest(params: IUpdateTimesheetStatus, bea
tenantId: params.tenantId,
})
}


export function createTimesheetRequest(params: UpdateTimesheet, bearer_token: string) {
return serverFetch<TimesheetLog>({
path: '/timesheet/time-log',
method: 'POST',
body: { ...params },
bearer_token,
tenantId: params.tenantId
})
}

export function updateTimesheetRequest(params: UpdateTimesheet, bearer_token: string) {
return serverFetch<TimesheetLog>({
path: `/timesheet/time-log/${params.id}`,
method: 'PUT',
body: { ...params },
bearer_token,
tenantId: params.tenantId
});
}
Loading

0 comments on commit 4eccf23

Please sign in to comment.