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

[Refactor]: Optimize DeleteTimesheet and UpdateTimesheetStatus #3375

Merged
merged 12 commits into from
Nov 27, 2024
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');

Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
try {
const { searchParams } = new URL(req.url);
const id = searchParams.get('id') as string;
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
const body = (await req.json()) as UpdateTimesheet;
const { data } = await updateTimesheetRequest(
{ ...body, tenantId, organizationId, id },
access_token
);
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
return $res(data);
} catch (error) {
console.error('Error updating timesheet status:', error);
return $res({
success: false,
message: 'Failed to update timesheet status'
});
}
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
}
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');
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved

try {
const body = (await req.json()) as UpdateTimesheet;
const { data } = await createTimesheetRequest(
{ ...body, tenantId, organizationId },
access_token
);
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
return $res(data);
} catch (error) {
console.error('Error updating timesheet status:', error);
return $res({
success: false,
message: 'Failed to update timesheet status'
});
}
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
}
105 changes: 67 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,65 @@ 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(async ({ ...timesheet }: UpdateTimesheet) => {
if (!user) return;
const response = await queryUpdateTimesheet(timesheet);
setTimesheet(prevTimesheet => [
response.data,
...prevTimesheet,
])
}, [queryUpdateTimesheet, setTimesheet, user])
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved


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 +226,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 +234,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 +263,7 @@ export function useTimesheet({

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

return {
loadingTimesheet,
Expand All @@ -251,6 +276,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
});
}
2 changes: 2 additions & 0 deletions apps/web/app/stores/time-logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface IFilterOption {
label: string;
}


export const timerLogsDailyReportState = atom<ITimerLogsDailyReport[]>([]);

export const timesheetRapportState = atom<TimesheetLog[]>([])
Expand All @@ -19,3 +20,4 @@ export const timesheetFilterStatusState = atom<IFilterOption[]>([]);
export const timesheetDeleteState = atom<string[]>([]);
export const timesheetGroupByDayState = atom<TimesheetFilterByDays>('Daily')
export const timesheetUpdateStatus = atom<UpdateTimesheetStatus[]>([])
export const timesheetUpdateState = atom<TimesheetLog | null>(null)
Loading
Loading