Skip to content

Commit bfb5c25

Browse files
Fix(timesheet): Correct handling of timesheet updates in state (#3497)
* fix(timesheet): correct handling of timesheet updates in state * cspall
1 parent 2adfad5 commit bfb5c25

File tree

5 files changed

+59
-35
lines changed

5 files changed

+59
-35
lines changed

apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
105105
source: TimerSource.BROWSER as any,
106106
taskId: formState.taskId,
107107
employeeId: formState.employeeId,
108-
organizationContactId: null || "",
109108
organizationTeamId: null,
110109
};
111110
try {
@@ -117,7 +116,6 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
117116
if (!shift.dateFrom || !shift.startTime || !shift.endTime) {
118117
throw new Error('Incomplete shift data.');
119118
}
120-
121119
const baseDate = shift.dateFrom instanceof Date ? shift.dateFrom : new Date(shift.dateFrom);
122120
const start = createUtcDate(baseDate, shift.startTime);
123121
const end = createUtcDate(baseDate, shift.endTime);
@@ -130,10 +128,11 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
130128
...payload,
131129
startedAt: start,
132130
stoppedAt: end,
131+
taskId: payload.taskId
133132
});
134133
})
135134
);
136-
console.log('Timesheets successfully created.');
135+
closeModal();
137136
} catch (error) {
138137
console.error('Failed to create timesheet:', error);
139138
}
@@ -154,7 +153,9 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
154153
<span className="text-[#de5505e1] ml-1">*</span>
155154
</label>
156155
<CustomSelect
157-
onChange={(value: any) => updateFormState('taskId', value.id)}
156+
onChange={(value) => {
157+
updateFormState('taskId', value)
158+
}}
158159
classNameGroup='h-[40vh]'
159160
ariaLabel='Task issues'
160161
className='w-full font-medium'

apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,9 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
193193
className="border border-transparent hover:border-transparent dark:hover:border-transparent"
194194
options={activeTeam?.members || []}
195195
value={timesheetData.employeeId}
196-
onChange={(value) => setTimesheetData({ ...timesheetData, employeeId: value.employeeId })}
196+
onChange={(value) => {
197+
setTimesheetData({ ...timesheetData, employeeId: value })
198+
}}
197199
renderOption={(option) => (
198200
<div className="flex items-center gap-x-2">
199201
<img className='h-6 w-6 rounded-full' src={option.employee.user.imageUrl} alt={option.employee.fullName} />

apps/web/app/[locale]/timesheet/[memberId]/components/RejectSelectedModal.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useTimesheet } from '@/app/hooks/features/useTimesheet';
22
import { clsxm } from '@/app/utils';
33
import { Modal } from '@/lib/components';
4+
import { ReloadIcon } from '@radix-ui/react-icons';
45
import { useTranslations } from 'next-intl';
56
import { useState } from 'react';
67
export interface IRejectSelectedModalProps {
@@ -33,7 +34,7 @@ export function RejectSelectedModal({
3334
}: IRejectSelectedModalProps) {
3435
const [isSubmitting, setIsSubmitting] = useState(false);
3536
const [reason, setReason] = useState('');
36-
const { updateTimesheetStatus, setSelectTimesheetId } = useTimesheet({});
37+
const { updateTimesheetStatus, loadingUpdateTimesheetStatus, setSelectTimesheetId } = useTimesheet({});
3738

3839
const t = useTranslations();
3940
const handleSubmit = async (e: React.FormEvent) => {
@@ -44,8 +45,8 @@ export function RejectSelectedModal({
4445
status: 'DENIED',
4546
ids: selectTimesheetId || [],
4647
}).then(() => {
47-
closeModal();
4848
setSelectTimesheetId([])
49+
closeModal();
4950
}).catch((error) => console.error(error));
5051
} finally {
5152
setIsSubmitting(false);
@@ -97,14 +98,17 @@ export function RejectSelectedModal({
9798
<button
9899
type="submit"
99100
disabled={
100-
isSubmitting || (minReasonLength !== undefined && reason.length < minReasonLength)
101+
loadingUpdateTimesheetStatus || isSubmitting || (minReasonLength !== undefined && reason.length < minReasonLength)
101102
}
102103
aria-label="Confirm rejection"
103104
className={clsxm(
104105
'bg-red-600 h-[2.2rem] font-normal flex items-center text-white px-2 rounded-lg',
105106
'disabled:opacity-50 disabled:cursor-not-allowed'
106107
)}
107108
>
109+
{loadingUpdateTimesheetStatus && (
110+
<ReloadIcon className="w-4 h-4 mr-2 animate-spin" />
111+
)}
108112
{isSubmitting ? 'Rejecting...' : 'Reject Entry'}
109113
</button>
110114
</div>

apps/web/app/[locale]/timesheet/[memberId]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
171171
}}
172172
/>
173173
<TimesheetCard
174-
hours={`${hours}:${minute}`}
174+
hours={`${String(hours).padStart(2, '0')}:${String(minute).padStart(2, '0')}`}
175175
title={t('common.MEN_HOURS')}
176176
date={`${moment(dateRange.from).format('YYYY-MM-DD')} - ${moment(dateRange.to).format('YYYY-MM-DD')}`}
177177
icon={<MenHoursIcon />}

apps/web/app/hooks/features/useTimesheet.ts

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { deleteTaskTimesheetLogsApi, getTaskTimesheetLogsApi, updateStatusTimesh
77
import moment from 'moment';
88
import { ID, TimesheetLog, TimesheetStatus, UpdateTimesheet } from '@/app/interfaces';
99
import { useTimelogFilterOptions } from './useTimelogFilterOptions';
10+
import axios from 'axios';
1011

1112
interface TimesheetParams {
1213
startDate?: Date | string;
@@ -25,18 +26,18 @@ const groupByDate = (items: TimesheetLog[]): GroupedTimesheet[] => {
2526
if (!items?.length) return [];
2627
type GroupedMap = Record<string, TimesheetLog[]>;
2728
const groupedByDate = items.reduce<GroupedMap>((acc, item) => {
28-
if (!item?.timesheet?.createdAt) {
29+
if (!item?.createdAt) {
2930
console.warn('Skipping item with missing timesheet or createdAt:', item);
3031
return acc;
3132
}
3233
try {
33-
const date = new Date(item.timesheet.createdAt).toISOString().split('T')[0];
34+
const date = new Date(item.createdAt).toISOString().split('T')[0];
3435
if (!acc[date]) acc[date] = [];
3536
acc[date].push(item);
3637
} catch (error) {
3738
console.error(
3839
`Failed to process date for timesheet ${item.timesheet.id}:`,
39-
{ createdAt: item.timesheet.createdAt, error }
40+
{ createdAt: item.createdAt, error }
4041
);
4142
}
4243
return acc;
@@ -61,19 +62,19 @@ const createGroupingFunction = (getKey: GroupingKeyFunction) => (items: Timeshee
6162
type GroupedMap = Record<string, TimesheetLog[]>;
6263

6364
const grouped = items.reduce<GroupedMap>((acc, item) => {
64-
if (!item?.timesheet?.createdAt) {
65+
if (!item?.createdAt) {
6566
console.warn('Skipping item with missing timesheet or createdAt:', item);
6667
return acc;
6768
}
6869
try {
69-
const date = new Date(item.timesheet.createdAt);
70+
const date = new Date(item.createdAt);
7071
const key = getKey(date);
7172
if (!acc[key]) acc[key] = [];
7273
acc[key].push(item);
7374
} catch (error) {
7475
console.error(
7576
`Failed to process date for timesheet ${item.timesheet.id}:`,
76-
{ createdAt: item.timesheet.createdAt, error }
77+
{ createdAt: item.createdAt, error }
7778
);
7879
}
7980
return acc;
@@ -177,40 +178,56 @@ export function useTimesheet({
177178
throw new Error("User not authenticated");
178179
}
179180
try {
180-
const response = await queryCreateTimesheet(timesheetParams);
181-
setTimesheet((prevTimesheet) => [
182-
response.data,
183-
...(prevTimesheet || [])
184-
]);
181+
const response = queryCreateTimesheet(timesheetParams).then((res) => {
182+
return res.data
183+
});
184+
return response
185185
} catch (error) {
186-
console.error('Error:', error);
186+
if (axios.isAxiosError(error)) {
187+
console.error('Axios Error:', {
188+
status: error.response?.status,
189+
statusText: error.response?.statusText,
190+
data: error.response?.data
191+
});
192+
throw new Error(`Request failed: ${error.message}`);
193+
}
194+
console.error('Error:', error instanceof Error ? error.message : error);
195+
throw error;
187196
}
188197
},
189198
[queryCreateTimesheet, setTimesheet, user]
190199
);
191200

192-
193-
194-
const updateTimesheet = useCallback<(params: UpdateTimesheet) => Promise<void>>(
195-
async ({ ...timesheet }: UpdateTimesheet) => {
201+
const updateTimesheet = useCallback(
202+
async (timesheet: UpdateTimesheet) => {
196203
if (!user) {
197-
throw new Error("User not authenticated");
204+
console.warn("User not authenticated!");
205+
return;
198206
}
199207
try {
200208
const response = await queryUpdateTimesheet(timesheet);
201-
setTimesheet((prevTimesheet) => {
202-
const updatedTimesheets = prevTimesheet.map((item) =>
203-
item.id === response.data.id
204-
? { ...item, ...response.data }
205-
: item
209+
if (response?.data?.id) {
210+
setTimesheet((prevTimesheet) =>
211+
prevTimesheet.map((item) =>
212+
item.id === response.data.id
213+
? { ...item, ...response.data }
214+
: item
215+
)
206216
);
207-
return updatedTimesheets;
208-
});
217+
} else {
218+
console.warn(
219+
"Unexpected structure of the response. No update performed.",
220+
response
221+
);
222+
}
209223
} catch (error) {
210-
console.error('Error updating timesheet:', error);
224+
console.error("Error updating the timesheet:", error);
211225
throw error;
212226
}
213-
}, [queryUpdateTimesheet, setTimesheet, user])
227+
},
228+
[queryUpdateTimesheet, setTimesheet, user]
229+
);
230+
214231

215232

216233
const updateTimesheetStatus = useCallback(

0 commit comments

Comments
 (0)