Skip to content

Commit 201c7bd

Browse files
authored
adding reset Users (#236)
* adding reset Users * changes done as per review
1 parent efe96f1 commit 201c7bd

3 files changed

Lines changed: 111 additions & 72 deletions

File tree

src/hooks/useChapterAssignment.ts

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
33
import { config } from '@/lib/config';
44
import { type ChapterAssignmentProgress, type UserChapterAssignment } from '@/lib/types';
55

6+
export interface AssignSelectedItem {
7+
chapterAssignmentId: number;
8+
drafterId: number | null;
9+
peerCheckerId: number | null;
10+
}
11+
612
export interface AssignChapterPayload {
7-
chapterAssignmentId: number[];
8-
userId: number;
9-
peerCheckerId?: number;
13+
assignments: AssignSelectedItem[];
1014
}
1115

1216
export interface ChapterAssignmentsByUser {
@@ -47,35 +51,28 @@ const fetchChapterAssignmentsByUserId = async (
4751
return (await res.json()) as ChapterAssignmentsByUser;
4852
};
4953

50-
export interface AssignChaptersResponse {
51-
success: boolean;
52-
message?: string;
53-
assignedUser?: string;
54-
peerChecker?: string;
55-
}
56-
5754
const assignChaptersToUser = async (
58-
payload: AssignChapterPayload & { email: string }
59-
): Promise<number[]> => {
60-
const { email, userId, chapterAssignmentId, peerCheckerId } = payload;
61-
62-
const response = await fetch(`${config.api.url}/users/${userId}/chapter-assignments`, {
63-
method: 'PATCH',
64-
headers: {
65-
'Content-Type': 'application/json',
66-
'x-user-email': email,
67-
},
68-
body: JSON.stringify({
69-
chapterAssignmentIds: chapterAssignmentId,
70-
peerCheckerId: peerCheckerId,
71-
}),
72-
});
55+
payload: AssignChapterPayload & { email: string; projectId: string }
56+
): Promise<UserChapterAssignment[]> => {
57+
const { email, projectId, assignments } = payload;
58+
59+
const response = await fetch(
60+
`${config.api.url}/projects/${projectId}/chapter-assignments/assign-selected`,
61+
{
62+
method: 'PATCH',
63+
headers: {
64+
'Content-Type': 'application/json',
65+
'x-user-email': email,
66+
},
67+
body: JSON.stringify({ assignments }),
68+
}
69+
);
7370

7471
if (!response.ok) {
7572
throw new Error('Failed to assign chapters');
7673
}
7774

78-
return (await response.json()) as number[];
75+
return (await response.json()) as UserChapterAssignment[];
7976
};
8077

8178
export const useChapterAssignments = (projectId: string, email: string) => {
@@ -117,21 +114,23 @@ export const useAssignChapters = (
117114

118115
if (previousAssignments) {
119116
const updatedAssignments = previousAssignments.map(assignment => {
120-
if (variables.chapterAssignmentId.includes(assignment.assignmentId)) {
121-
return {
122-
...assignment,
123-
...(assignedUserName &&
124-
variables.userId && {
125-
assignedUser: { id: variables.userId, displayName: assignedUserName },
126-
}),
127-
...(peerCheckerName &&
128-
variables.peerCheckerId && {
129-
peerChecker: { id: variables.peerCheckerId, displayName: peerCheckerName },
130-
}),
131-
status: assignment.status === 'not_started' ? 'draft' : assignment.status,
132-
};
133-
}
134-
return assignment;
117+
const item = variables.assignments.find(
118+
a => a.chapterAssignmentId === assignment.assignmentId
119+
);
120+
if (!item) return assignment;
121+
122+
return {
123+
...assignment,
124+
assignedUser:
125+
item.drafterId !== null && assignedUserName
126+
? { id: item.drafterId, displayName: assignedUserName }
127+
: null,
128+
peerChecker:
129+
item.peerCheckerId !== null && peerCheckerName
130+
? { id: item.peerCheckerId, displayName: peerCheckerName }
131+
: null,
132+
status: assignment.status === 'not_started' ? 'draft' : assignment.status,
133+
};
135134
});
136135

137136
queryClient.setQueryData(['chapterAssignments', projectId, email], updatedAssignments);
@@ -140,7 +139,6 @@ export const useAssignChapters = (
140139
return { previousAssignments };
141140
},
142141
onError: (_err, _variables, context) => {
143-
// Rollback on error
144142
if (context?.previousAssignments) {
145143
queryClient.setQueryData(
146144
['chapterAssignments', projectId, email],
@@ -153,8 +151,16 @@ export const useAssignChapters = (
153151
queryKey: ['chapterAssignments', projectId, email],
154152
});
155153

156-
void queryClient.invalidateQueries({
157-
queryKey: ['userChapterAssignments', variables.userId],
154+
// Invalidate user assignment caches for every unique user touched
155+
const affectedUserIds = new Set<number>();
156+
variables.assignments.forEach(a => {
157+
if (a.drafterId !== null) affectedUserIds.add(a.drafterId);
158+
if (a.peerCheckerId !== null) affectedUserIds.add(a.peerCheckerId);
159+
});
160+
affectedUserIds.forEach(userId => {
161+
void queryClient.invalidateQueries({
162+
queryKey: ['userChapterAssignments', userId],
163+
});
158164
});
159165

160166
void queryClient.invalidateQueries({

src/layouts/projects/AssignUsersDialog.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ export const AssignUsersDialog: React.FC<AssignUsersDialogProps> = ({
116116
const hasCompleteStatus = selectedAssignmentsStatuses.some(
117117
status => status === ChapterAssignmentStatus.COMPLETE
118118
);
119+
const isFormEmpty = !selectedDrafter && !selectedPeerChecker;
120+
const hasCompleteSelection = !!selectedDrafter && !!selectedPeerChecker;
121+
const isDraftingComplete = selectedAssignmentsStatuses.some(
122+
status =>
123+
status !== ChapterAssignmentStatus.NOT_STARTED && status !== ChapterAssignmentStatus.DRAFT
124+
);
125+
const isResetDisabled = isFormEmpty || isDraftingComplete || usersLoading || isAssigning;
126+
127+
const canSubmit = isFormEmpty || hasCompleteSelection;
128+
const isSubmitDisabled = !canSubmit || usersLoading || isAssigning;
129+
130+
const handleReset = () => {
131+
onDrafterChange('');
132+
onPeerCheckerChange('');
133+
};
119134

120135
const isDrafterDisabled =
121136
hasPeerCheckStatus ||
@@ -231,15 +246,17 @@ export const AssignUsersDialog: React.FC<AssignUsersDialogProps> = ({
231246
</div>
232247

233248
<DialogFooter className='flex gap-2'>
234-
<Button
235-
disabled={!selectedDrafter || !selectedPeerChecker || usersLoading || isAssigning}
236-
onClick={onAssign}
237-
>
249+
<Button disabled={isResetDisabled} onClick={handleReset}>
250+
Reset Users
251+
</Button>
252+
<Button disabled={isSubmitDisabled} onClick={onAssign}>
238253
{isAssigning ? (
239254
<>
240255
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
241-
Assigning...
256+
{isFormEmpty ? 'Unassigning...' : 'Assigning...'}{' '}
242257
</>
258+
) : isFormEmpty ? (
259+
'Unassign Users'
243260
) : (
244261
'Assign Users'
245262
)}

src/layouts/projects/ProjectDetailPage.tsx

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -386,39 +386,55 @@ export const ProjectDetailPage: React.FC<ProjectDetailPageProps> = ({
386386
}, [selectedAssignments, chapterAssignments, projectUsers]);
387387

388388
const handleAssignUser = useCallback(async () => {
389-
if (selectedDrafter && selectedAssignments.length > 0 && userdetail?.email) {
390-
try {
391-
await assignChapterMutation.mutateAsync({
392-
chapterAssignmentId: selectedAssignments,
393-
userId: parseInt(selectedDrafter),
394-
peerCheckerId: parseInt(selectedPeerChecker),
395-
email: userdetail.email,
396-
});
397-
398-
setIsRefreshingAfterAssignment(true);
399-
400-
setSelectedDrafter('');
401-
setSelectedPeerChecker('');
402-
setSelectedAssignments([]);
403-
setSelectedAssignmentsStatuses([]);
404-
setIsDialogOpen(false);
405-
} catch (error) {
406-
Logger.logException(error, { context: 'Error Assigning Chapters' });
407-
setIsRefreshingAfterAssignment(false);
408-
}
389+
const drafterId = selectedDrafter === '' ? null : parseInt(selectedDrafter);
390+
const peerCheckerId = selectedPeerChecker === '' ? null : parseInt(selectedPeerChecker);
391+
const isUnassigning = drafterId === null && peerCheckerId === null;
392+
const canProceed =
393+
(drafterId !== null || isUnassigning) &&
394+
selectedAssignments.length > 0 &&
395+
userdetail?.email &&
396+
projectId;
397+
398+
if (!canProceed) return;
399+
400+
try {
401+
await assignChapterMutation.mutateAsync({
402+
projectId: projectId.toString(),
403+
email: userdetail.email,
404+
assignments: selectedAssignments.map(id => ({
405+
chapterAssignmentId: id,
406+
drafterId,
407+
peerCheckerId,
408+
})),
409+
});
410+
411+
setIsRefreshingAfterAssignment(true);
412+
setSelectedDrafter('');
413+
setSelectedPeerChecker('');
414+
setSelectedAssignments([]);
415+
setSelectedAssignmentsStatuses([]);
416+
setIsDialogOpen(false);
417+
} catch (error) {
418+
Logger.logException(error, { context: 'Error Assigning/Unassigning Chapters' });
419+
setIsRefreshingAfterAssignment(false);
409420
}
410421
}, [
411422
selectedDrafter,
412423
selectedPeerChecker,
413424
selectedAssignments,
414425
userdetail?.email,
426+
projectId,
415427
assignChapterMutation,
416428
]);
417429

418430
const handleCheckboxChange = useCallback((assignmentId: number, checked: boolean) => {
419-
setSelectedAssignments(prev =>
420-
checked ? [...prev, assignmentId] : prev.filter(id => id !== assignmentId)
421-
);
431+
setSelectedAssignments(prev => {
432+
if (checked) {
433+
return prev.includes(assignmentId) ? prev : [...prev, assignmentId];
434+
} else {
435+
return prev.filter(id => id !== assignmentId);
436+
}
437+
});
422438
}, []);
423439

424440
if (isRefreshingAfterAssignment && !assignmentsFetching) {

0 commit comments

Comments
 (0)