ENCOA-310
This commit is contained in:
@@ -18,6 +18,7 @@ import UserTable from "../Tables/UserTable";
|
||||
import ClassroomImportSummary from "../ImportSummaries/Classroom";
|
||||
import Select from "../Low/Select";
|
||||
import { EntityWithRoles } from "@/interfaces/entity";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
|
||||
const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/);
|
||||
@@ -47,7 +48,7 @@ export interface ClassroomTransferState {
|
||||
otherEntityUsers: UserImport[];
|
||||
alreadyInClass: UserImport[];
|
||||
notOwnedClassrooms: string[];
|
||||
classroomsToCreate: string[];
|
||||
validClassrooms: string[];
|
||||
}
|
||||
|
||||
|
||||
@@ -66,9 +67,11 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
otherEntityUsers: [],
|
||||
alreadyInClass: [],
|
||||
notOwnedClassrooms: [],
|
||||
classroomsToCreate: []
|
||||
validClassrooms: []
|
||||
})
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { openFilePicker, filesContent, clear } = useFilePicker({
|
||||
accept: ".xlsx",
|
||||
multiple: false,
|
||||
@@ -183,7 +186,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filesContent]);
|
||||
}, [filesContent, entity]);
|
||||
|
||||
// Stage 1 - Excel Parsing
|
||||
// - Group rows by emails
|
||||
@@ -299,10 +302,10 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
notFoundUsers: [],
|
||||
otherEntityUsers: [],
|
||||
notOwnedClassrooms: [],
|
||||
classroomsToCreate: [],
|
||||
validClassrooms: [],
|
||||
}));
|
||||
}
|
||||
}, [classroomTransferState.parsedExcel]);
|
||||
}, [classroomTransferState.parsedExcel, entity]);
|
||||
|
||||
// Stage 2 - Student Filter
|
||||
// - Filter non existant students
|
||||
@@ -377,7 +380,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
...prev,
|
||||
stage: 3,
|
||||
notOwnedClassrooms: notOwnedClassroomsSameName,
|
||||
classroomsToCreate: Array.from(classrooms).filter(
|
||||
validClassrooms: Array.from(classrooms).filter(
|
||||
(name) => !new Set(notOwnedClassroomsSameName).has(name)
|
||||
)
|
||||
}))
|
||||
@@ -388,7 +391,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
if (classroomTransferState.imports.length > 0 && classroomTransferState.stage === 2) {
|
||||
crossRefClassrooms();
|
||||
}
|
||||
}, [classroomTransferState.imports, classroomTransferState.stage, user.id])
|
||||
}, [classroomTransferState.imports, classroomTransferState.stage, user.id, entity])
|
||||
|
||||
|
||||
const clearAndReset = () => {
|
||||
@@ -403,7 +406,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
otherEntityUsers: [],
|
||||
alreadyInClass: [],
|
||||
notOwnedClassrooms: [],
|
||||
classroomsToCreate: []
|
||||
validClassrooms: []
|
||||
});
|
||||
clear();
|
||||
};
|
||||
@@ -422,7 +425,6 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
const getIds = async () => {
|
||||
try {
|
||||
const { data: emailIdMap } = await axios.post("/api/users/controller?op=getIds", {
|
||||
@@ -445,7 +447,6 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
|
||||
if (!imports) return;
|
||||
|
||||
|
||||
await axios.post("/api/groups/controller?op=deletePriorEntitiesGroups", {
|
||||
ids: imports.map((u) => u.id),
|
||||
entity
|
||||
@@ -456,6 +457,9 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
entity
|
||||
});
|
||||
|
||||
const { data: existingGroupsMap } = await axios.post("/api/groups/controller?op=existantGroupIds", {
|
||||
names: classroomTransferState.validClassrooms
|
||||
});
|
||||
|
||||
const groupedUsers = imports.reduce((acc, user) => {
|
||||
if (!acc[user.groupName]) {
|
||||
@@ -464,12 +468,18 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
acc[user.groupName].push(user);
|
||||
return acc;
|
||||
}, {} as Record<string, UserImport[]>);
|
||||
|
||||
|
||||
// ##############
|
||||
// # New Groups #
|
||||
// ##############
|
||||
const newGroupUsers = Object.fromEntries(
|
||||
Object.entries(groupedUsers)
|
||||
.filter(([groupName]) => classroomTransferState.classroomsToCreate.includes(groupName))
|
||||
.filter(([groupName]) =>
|
||||
classroomTransferState.validClassrooms.includes(groupName) &&
|
||||
!existingGroupsMap[groupName]
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
const createGroupPromises = Object.entries(newGroupUsers).map(([groupName, users]) => {
|
||||
const groupData: Partial<Group> = {
|
||||
admin: user.id,
|
||||
@@ -479,25 +489,33 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
};
|
||||
return axios.post('/api/groups', groupData);
|
||||
});
|
||||
|
||||
|
||||
const existingGroupUsers = Object.fromEntries(
|
||||
|
||||
// ###################
|
||||
// # Existant Groups #
|
||||
// ###################
|
||||
const allExistingUsers = Object.fromEntries(
|
||||
Object.entries(groupedUsers)
|
||||
.filter(([groupName]) => !classroomTransferState.classroomsToCreate.includes(groupName))
|
||||
.filter(([groupName]) =>
|
||||
!classroomTransferState.validClassrooms.includes(groupName) ||
|
||||
existingGroupsMap[groupName]
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
let updatePromises: Promise<any>[] = [];
|
||||
|
||||
if (Object.keys(allExistingUsers).length > 0) {
|
||||
const { data: { groups: groupNameToId, users: userEmailToId } } = await axios.post('/api/groups/controller?op=getIds', {
|
||||
names: Object.keys(allExistingUsers),
|
||||
userEmails: Object.values(allExistingUsers).flat().map(user => user.email)
|
||||
});
|
||||
|
||||
if (Object.keys(existingGroupUsers).length > 0) {
|
||||
const { groups: groupNameToId, users: userEmailToId } = await axios.post('/api/groups?op=getIds', {
|
||||
names: Object.keys(existingGroupUsers),
|
||||
userEmails: Object.values(existingGroupUsers).flat().map(user => user.email)
|
||||
}).then(response => response.data);
|
||||
|
||||
const existingGroupsWithIds = Object.entries(existingGroupUsers).reduce((acc, [groupName, users]) => {
|
||||
acc[groupNameToId[groupName]] = users;
|
||||
const existingGroupsWithIds = Object.entries(allExistingUsers).reduce((acc, [groupName, users]) => {
|
||||
const groupId = groupNameToId[groupName];
|
||||
if (groupId) {
|
||||
acc[groupId] = users;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, any[]>);
|
||||
}, {} as Record<string, UserImport[]>);
|
||||
|
||||
updatePromises = Object.entries(existingGroupsWithIds).map(([groupId, users]) => {
|
||||
const userIds = users.map(user => userEmailToId[user.email]);
|
||||
@@ -506,13 +524,14 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
await Promise.all([
|
||||
...createGroupPromises,
|
||||
...updatePromises
|
||||
]);
|
||||
|
||||
toast.success(`Successfully assigned all ${classroomTransferState.imports.length} user(s)!`);
|
||||
router.replace("/classrooms");
|
||||
onFinish();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -19,6 +19,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
else if (req.method === 'POST') {
|
||||
switch (op) {
|
||||
case 'existantGroupIds':
|
||||
res.status(200).json(await existantGroupIds(req.body.names));
|
||||
break;
|
||||
case 'crossRefOwnership':
|
||||
res.status(200).json(await crossRefOwnership(req.body));
|
||||
break;
|
||||
@@ -41,7 +44,7 @@ async function crossRefOwnership(body: any): Promise<string[]> {
|
||||
const { userId, classrooms, entity } = body;
|
||||
|
||||
const existingClassrooms = await db.collection('groups')
|
||||
.find({
|
||||
.find({
|
||||
name: { $in: classrooms },
|
||||
admin: { $ne: userId }
|
||||
})
|
||||
@@ -61,7 +64,7 @@ async function crossRefOwnership(body: any): Promise<string[]> {
|
||||
|
||||
const adminEntitiesMap = new Map(
|
||||
adminUsers.map(admin => [
|
||||
admin.id,
|
||||
admin.id,
|
||||
admin.entities?.map((e: any) => e.id) || []
|
||||
])
|
||||
);
|
||||
@@ -104,7 +107,6 @@ async function getIds(body: any): Promise<Record<string, string>> {
|
||||
|
||||
async function deletePriorEntitiesGroups(body: any) {
|
||||
const { ids, entity } = body;
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0 || !entity) {
|
||||
return;
|
||||
}
|
||||
@@ -123,8 +125,33 @@ async function deletePriorEntitiesGroups(body: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.collection('groups').updateMany(
|
||||
const affectedGroups = await db.collection('groups')
|
||||
.find({ participants: { $in: toDeleteUserIds } })
|
||||
.project({ id: 1, _id: 0 })
|
||||
.toArray();
|
||||
|
||||
await db.collection('groups').updateMany(
|
||||
{ participants: { $in: toDeleteUserIds } },
|
||||
{ $pull: { participants: { $in: toDeleteUserIds } } } as any
|
||||
);
|
||||
|
||||
// delete groups that were updated and have no participants
|
||||
await db.collection('groups').deleteMany({
|
||||
id: { $in: affectedGroups.map(g => g.id) },
|
||||
participants: { $size: 0 }
|
||||
});
|
||||
}
|
||||
|
||||
async function existantGroupIds(names: string[]) {
|
||||
const existingGroups = await db.collection('groups')
|
||||
.find({
|
||||
name: { $in: names }
|
||||
})
|
||||
.project({ id: 1, name: 1, _id: 0 })
|
||||
.toArray();
|
||||
|
||||
return existingGroups.reduce((acc, group) => {
|
||||
acc[group.name] = group.id;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user