Skip to content

Commit 52adf07

Browse files
committed
Merge branch 'development' of https://github.com/OneCommunityGlobal/HighestGoodNetworkApp into Sanjeev-fix-create-new-event-button
2 parents 5f61636 + 1f8b049 commit 52adf07

52 files changed

Lines changed: 3885 additions & 1307 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

git_log.txt

2.32 KB
Binary file not shown.

src/actions/__tests__/userManagement.test.js

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,33 +35,6 @@ describe('User Management Actions', () => {
3535
});
3636
});
3737

38-
describe('updateUserStatus', () => {
39-
const mockUser = {
40-
_id: '123',
41-
name: 'John Doe',
42-
createdDate: '2024-01-01',
43-
isActive: true
44-
};
45-
46-
47-
it('should update user to active status', async () => {
48-
const reactivationDate = null;
49-
50-
axios.patch.mockResolvedValueOnce({ data: {} });
51-
52-
await store.dispatch(actions.updateUserStatus(mockUser, UserStatus.Active, reactivationDate));
53-
54-
expect(axios.patch).toHaveBeenCalledWith(
55-
ENDPOINTS.USER_PROFILE(mockUser._id),
56-
{
57-
status: UserStatus.Active,
58-
reactivationDate: null,
59-
endDate: undefined
60-
}
61-
);
62-
});
63-
});
64-
6538
describe('updateRehireableStatus', () => {
6639
it('should update rehireable status successfully', async () => {
6740
const mockUser = { _id: '123', name: 'John Doe' };
@@ -91,8 +64,9 @@ describe('User Management Actions', () => {
9164

9265
axios.patch.mockRejectedValueOnce(error);
9366

94-
await expect(store.dispatch(actions.updateRehireableStatus(mockUser, isRehireable)))
95-
.rejects.toThrow('Update failed');
67+
await expect(
68+
store.dispatch(actions.updateRehireableStatus(mockUser, isRehireable))
69+
).rejects.toThrow('Update failed');
9670
});
9771
});
9872

@@ -111,7 +85,6 @@ describe('User Management Actions', () => {
11185
];
11286

11387
await store.dispatch(actions.toggleVisibility(mockUser, newVisibility));
114-
expect(store.getActions()).toEqual(expectedActions);
11588
expect(axios.patch).toHaveBeenCalledWith(
11689
ENDPOINTS.TOGGLE_VISIBILITY(mockUser._id),
11790
{ isVisible: newVisibility }
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import moment from 'moment';
2+
import { toast } from 'react-toastify';
3+
import { updateUserLifecycle, buildUpdatedUserLifecycleDetails } from './userManagement';
4+
import { UserStatusOperations } from '../utils/enums';
5+
6+
export const scheduleDeactivationAction = async (
7+
dispatch,
8+
userProfile,
9+
endDate,
10+
loadUserProfile
11+
) => {
12+
const action = UserStatusOperations.SCHEDULE_DEACTIVATION;
13+
const updatedUser = buildUpdatedUserLifecycleDetails(userProfile, {
14+
action,
15+
endDate,
16+
});
17+
18+
try {
19+
await dispatch(
20+
updateUserLifecycle(updatedUser, {
21+
action,
22+
endDate,
23+
originalUser: userProfile,
24+
})
25+
);
26+
27+
toast.success('User deactivation scheduled.');
28+
if (loadUserProfile) await loadUserProfile();
29+
} catch (err) {
30+
toast.error('Failed to schedule deactivation.');
31+
throw err;
32+
}
33+
};
34+
35+
export const activateUserAction = async (
36+
dispatch,
37+
userProfile,
38+
loadUserProfile
39+
) => {
40+
const action = UserStatusOperations.ACTIVATE;
41+
const updatedUser = buildUpdatedUserLifecycleDetails(userProfile, { action });
42+
43+
try {
44+
await dispatch(
45+
updateUserLifecycle(updatedUser, {
46+
action,
47+
originalUser: userProfile,
48+
})
49+
);
50+
51+
toast.success('User activated.');
52+
if (loadUserProfile) await loadUserProfile();
53+
} catch (err) {
54+
toast.error('Failed to activate user.');
55+
throw err;
56+
}
57+
};
58+
59+
export const deactivateImmediatelyAction = async (
60+
dispatch,
61+
userProfile,
62+
loadUserProfile
63+
) => {
64+
const action = UserStatusOperations.DEACTIVATE;
65+
const updatedUser = buildUpdatedUserLifecycleDetails(userProfile, { action });
66+
try {
67+
if(!userProfile.endDate){
68+
// required when called from UserManagement table
69+
userProfile.endDate = moment().toISOString();
70+
}
71+
await dispatch(
72+
updateUserLifecycle(updatedUser, {
73+
action,
74+
originalUser: userProfile,
75+
})
76+
);
77+
78+
toast.success('User deactivated successfully.');
79+
if (loadUserProfile) await loadUserProfile();
80+
} catch (err) {
81+
toast.error('Failed to deactivate user.');
82+
throw err;
83+
}
84+
};
85+
86+
export const pauseUserAction = async (
87+
dispatch,
88+
userProfile,
89+
reactivationDate,
90+
loadUserProfile
91+
) => {
92+
const action = UserStatusOperations.PAUSE;
93+
const updatedUser = buildUpdatedUserLifecycleDetails(userProfile, {
94+
action,
95+
reactivationDate,
96+
});
97+
98+
try {
99+
await dispatch(
100+
updateUserLifecycle(updatedUser, {
101+
action,
102+
reactivationDate,
103+
originalUser: userProfile,
104+
})
105+
);
106+
107+
toast.success('User paused successfully.');
108+
if (loadUserProfile) await loadUserProfile();
109+
} catch (err) {
110+
toast.error('Failed to pause user.');
111+
throw err;
112+
}
113+
};

src/actions/userManagement.js

Lines changed: 116 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import {
1616
START_USER_INFO_UPDATE,
1717
} from '../constants/userManagement';
1818
import { ENDPOINTS } from '~/utils/URL';
19-
import { UserStatus } from '~/utils/enums';
20-
import { getTimeEndDateEntriesByPeriod } from './timeEntries';
19+
import { UserStatus, UserStatusOperations, InactiveReason } from '~/utils/enums';
20+
import { COMPANY_TZ } from '~/utils/formatDate';
2121

2222
/**
2323
* Set a flag that fetching user profiles
@@ -129,49 +129,125 @@ export const getAllUserProfile = () => {
129129
* @param {*} user - the user to be updated
130130
* @param {*} status - Active/InActive
131131
*/
132-
export const updateUserStatus = (user, status, reactivationDate) => {
133-
const userProfile = { ...user };
134-
userProfile.isActive = status === UserStatus.Active;
135-
userProfile.reactivationDate = reactivationDate;
136-
const patchData = { status, reactivationDate };
132+
133+
const resolveEndDate = (user) => {
134+
135+
//1) endDate + lastActivityAt -> earlier of the two
136+
if (user?.endDate && user?.lastActivityAt) {
137+
return moment.min(moment(user.endDate), moment(user.lastActivityAt)).toISOString();
138+
}
139+
140+
//2) if no lastActivityAt, use createdDate (if present)
141+
// format createdDate to real datetime in COMPANY_TZ
142+
if(!user?.lastActivityAt && user?.createdDate) {
143+
const created = moment.tz(user.createdDate, 'YYYY-MM-DD', COMPANY_TZ).startOf('day');
144+
return created.toISOString();
145+
}
146+
147+
//3) if no createdDate -> endDate will be set as passed
148+
if(user?.endDate) {
149+
return moment(user.endDate).toISOString();
150+
}
151+
152+
// optional: if lastActivityAt is present, use that
153+
if(user?.lastActivityAt) {
154+
return moment(user.lastActivityAt).toISOString();
155+
}
156+
157+
// if everything is missing, fall back to current date
158+
return moment().tz(COMPANY_TZ).toISOString();
159+
}
160+
161+
export const buildUpdatedUserLifecycleDetails = (user, payload) => {
162+
const {action, endDate, reactivationDate} = payload;
163+
switch (action) {
164+
case UserStatusOperations.ACTIVATE:
165+
return {
166+
...user,
167+
isActive: true,
168+
inactiveReason: null,
169+
endDate: null,
170+
reactivationDate: null,
171+
};
172+
173+
case UserStatusOperations.DEACTIVATE: {
174+
const resolvedEndDate = resolveEndDate(user);
175+
return {
176+
...user,
177+
isActive: false,
178+
inactiveReason: InactiveReason.SEPARATED,
179+
endDate: resolvedEndDate
180+
};
181+
}
182+
183+
case UserStatusOperations.SCHEDULE_DEACTIVATION:
184+
return {
185+
...user,
186+
isActive: true,
187+
inactiveReason: InactiveReason.SCHEDULED_SEPARATION,
188+
endDate: endDate || user.endDate,
189+
};
190+
191+
case UserStatusOperations.PAUSE:
192+
return {
193+
...user,
194+
isActive: false,
195+
inactiveReason: InactiveReason.PAUSED,
196+
reactivationDate: reactivationDate || user.reactivationDate,
197+
endDate: null,
198+
};
199+
200+
default:
201+
return user;
202+
}
203+
};
204+
205+
const buildBackendPayload = (userDetails, action) => {
206+
console.log('Building backend payload with:', { userDetails, action });
207+
switch (action){
208+
case UserStatusOperations.ACTIVATE:
209+
return {
210+
action: action,
211+
status: UserStatus.Active,
212+
endDate: null,
213+
};
214+
case UserStatusOperations.DEACTIVATE:
215+
return {
216+
action: action,
217+
status: UserStatus.Inactive,
218+
endDate: userDetails.endDate,
219+
};
220+
case UserStatusOperations.SCHEDULE_DEACTIVATION:
221+
return {
222+
action: action,
223+
endDate: userDetails.endDate,
224+
};
225+
case UserStatusOperations.PAUSE:
226+
return {
227+
action: action,
228+
reactivationDate: userDetails.reactivationDate
229+
};
230+
default:
231+
throw new Error(`Unknown lifecycle action: ${action}`);
232+
}
233+
};
234+
235+
export const updateUserLifecycle = (updatedUser, payload) => {
137236
return async dispatch => {
138-
// Optimistic update
139-
dispatch(userProfileUpdateAction(userProfile));
237+
dispatch(userProfileUpdateAction(updatedUser));
140238

239+
const backendPayload = buildBackendPayload(updatedUser, payload.action);
141240
try {
142-
if (status === UserStatus.InActive) {
143-
// Check for the last week of work
144-
const lastEnddate = await dispatch(
145-
getTimeEndDateEntriesByPeriod(user._id, user.createdDate, userProfile.toDate),
146-
);
147-
if (lastEnddate !== 'N/A') {
148-
// if work exists, set EndDate to that week
149-
patchData.endDate = moment(lastEnddate).format('YYYY-MM-DDTHH:mm:ss');
150-
userProfile.endDate = moment(lastEnddate).format('YYYY-MM-DDTHH:mm:ss');
151-
} else {
152-
// No work exists, set end date to start date
153-
patchData.endDate = moment(user.createdDate);
154-
userProfile.endDate = moment(user.createdDate);
155-
}
156-
} else {
157-
// User is active
158-
patchData.endDate = undefined;
159-
userProfile.endDate = undefined;
160-
}
161-
162-
// Perform the API call
163-
await axios.patch(ENDPOINTS.USER_PROFILE(user._id), patchData);
164-
165-
// Ensure the dispatched action is final (optional if optimistic update is sufficient)
166-
dispatch(userProfileUpdateAction(userProfile));
167-
} catch (error) {
168-
toast.error('Error updating user status:', error);
241+
// console.log('Sending PATCH request to update user lifecycle');
242+
await axios.patch(ENDPOINTS.USER_PROFILE(updatedUser._id), backendPayload);
169243

170-
// Rollback to previous state on error
171-
dispatch(userProfileUpdateAction(user));
244+
} catch (error) {
245+
toast.error('Error updating user lifecycle:', error);
246+
dispatch(userProfileUpdateAction(payload.originalUser));
247+
throw error;
172248
}
173249
};
174-
};
250+
}
175251

176252
/**
177253
* Update the rehireable status of a user
@@ -284,28 +360,6 @@ export const updateUserFinalDayStatusIsSet = (user, status, finalDayDate, isSet)
284360
};
285361
};
286362

287-
export const updateUserFinalDay = (user, finalDayDate, isSet) => {
288-
return async dispatch => {
289-
try {
290-
const patchData = {
291-
endDate: finalDayDate ? new Date(finalDayDate) : undefined,
292-
isSet,
293-
};
294-
const response = await axios.patch(ENDPOINTS.UPDATE_USER_FINAL_DAY(user._id), patchData);
295-
296-
const updatedUserProfile = {
297-
...user,
298-
...response.data,
299-
};
300-
301-
dispatch(userProfileUpdateAction(updatedUserProfile));
302-
} catch (error) {
303-
toast.error('Error updating user profile:', error);
304-
throw new Error('Failed to update user profile.');
305-
}
306-
};
307-
};
308-
309363

310364
/**
311365
* fetching all user profiles basic info
@@ -352,16 +406,4 @@ export const changePagination = value => dispatch => {
352406

353407
export const updateUserInfomation = value => dispatch => {
354408
dispatch({ type: START_USER_INFO_UPDATE, payload: value });
355-
};
356-
357-
// export const updateUserInformation=(value)=>async(dispatch,getState)=>{
358-
// try {
359-
// dispatch({type:START_USER_INFO_UPDATE})
360-
// var response=await axios.patch(ENDPOINTS.USER_PROFILE_UPDATE,value);
361-
// const {data} = await axios.get(ENDPOINTS.USER_PROFILES);
362-
// dispatch({type:FINISH_USER_INFO_UPDATE,payload:data})
363-
// } catch (error) {
364-
// console.log(error)
365-
// dispatch({type:ERROR_USER_INFO_UPDATE})
366-
// }
367-
// }
409+
};

0 commit comments

Comments
 (0)