ENCOA-310

This commit is contained in:
Carlos-Mesquita
2025-01-05 22:35:57 +00:00
parent 8f77f28aaa
commit bc89f4b9ce
2 changed files with 78 additions and 32 deletions

View File

@@ -18,6 +18,7 @@ import UserTable from "../Tables/UserTable";
import ClassroomImportSummary from "../ImportSummaries/Classroom"; import ClassroomImportSummary from "../ImportSummaries/Classroom";
import Select from "../Low/Select"; import Select from "../Low/Select";
import { EntityWithRoles } from "@/interfaces/entity"; 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]+)*$/); 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[]; otherEntityUsers: UserImport[];
alreadyInClass: UserImport[]; alreadyInClass: UserImport[];
notOwnedClassrooms: string[]; notOwnedClassrooms: string[];
classroomsToCreate: string[]; validClassrooms: string[];
} }
@@ -66,9 +67,11 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
otherEntityUsers: [], otherEntityUsers: [],
alreadyInClass: [], alreadyInClass: [],
notOwnedClassrooms: [], notOwnedClassrooms: [],
classroomsToCreate: [] validClassrooms: []
}) })
const router = useRouter();
const { openFilePicker, filesContent, clear } = useFilePicker({ const { openFilePicker, filesContent, clear } = useFilePicker({
accept: ".xlsx", accept: ".xlsx",
multiple: false, multiple: false,
@@ -183,7 +186,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [filesContent]); }, [filesContent, entity]);
// Stage 1 - Excel Parsing // Stage 1 - Excel Parsing
// - Group rows by emails // - Group rows by emails
@@ -299,10 +302,10 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
notFoundUsers: [], notFoundUsers: [],
otherEntityUsers: [], otherEntityUsers: [],
notOwnedClassrooms: [], notOwnedClassrooms: [],
classroomsToCreate: [], validClassrooms: [],
})); }));
} }
}, [classroomTransferState.parsedExcel]); }, [classroomTransferState.parsedExcel, entity]);
// Stage 2 - Student Filter // Stage 2 - Student Filter
// - Filter non existant students // - Filter non existant students
@@ -377,7 +380,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
...prev, ...prev,
stage: 3, stage: 3,
notOwnedClassrooms: notOwnedClassroomsSameName, notOwnedClassrooms: notOwnedClassroomsSameName,
classroomsToCreate: Array.from(classrooms).filter( validClassrooms: Array.from(classrooms).filter(
(name) => !new Set(notOwnedClassroomsSameName).has(name) (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) { if (classroomTransferState.imports.length > 0 && classroomTransferState.stage === 2) {
crossRefClassrooms(); crossRefClassrooms();
} }
}, [classroomTransferState.imports, classroomTransferState.stage, user.id]) }, [classroomTransferState.imports, classroomTransferState.stage, user.id, entity])
const clearAndReset = () => { const clearAndReset = () => {
@@ -403,7 +406,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
otherEntityUsers: [], otherEntityUsers: [],
alreadyInClass: [], alreadyInClass: [],
notOwnedClassrooms: [], notOwnedClassrooms: [],
classroomsToCreate: [] validClassrooms: []
}); });
clear(); clear();
}; };
@@ -422,7 +425,6 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
try { try {
setIsLoading(true); setIsLoading(true);
const getIds = async () => { const getIds = async () => {
try { try {
const { data: emailIdMap } = await axios.post("/api/users/controller?op=getIds", { 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; if (!imports) return;
await axios.post("/api/groups/controller?op=deletePriorEntitiesGroups", { await axios.post("/api/groups/controller?op=deletePriorEntitiesGroups", {
ids: imports.map((u) => u.id), ids: imports.map((u) => u.id),
entity entity
@@ -456,6 +457,9 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
entity entity
}); });
const { data: existingGroupsMap } = await axios.post("/api/groups/controller?op=existantGroupIds", {
names: classroomTransferState.validClassrooms
});
const groupedUsers = imports.reduce((acc, user) => { const groupedUsers = imports.reduce((acc, user) => {
if (!acc[user.groupName]) { if (!acc[user.groupName]) {
@@ -464,12 +468,18 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
acc[user.groupName].push(user); acc[user.groupName].push(user);
return acc; return acc;
}, {} as Record<string, UserImport[]>); }, {} as Record<string, UserImport[]>);
// ##############
// # New Groups #
// ##############
const newGroupUsers = Object.fromEntries( const newGroupUsers = Object.fromEntries(
Object.entries(groupedUsers) 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 createGroupPromises = Object.entries(newGroupUsers).map(([groupName, users]) => {
const groupData: Partial<Group> = { const groupData: Partial<Group> = {
admin: user.id, admin: user.id,
@@ -479,25 +489,33 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
}; };
return axios.post('/api/groups', groupData); return axios.post('/api/groups', groupData);
}); });
// ###################
const existingGroupUsers = Object.fromEntries( // # Existant Groups #
// ###################
const allExistingUsers = Object.fromEntries(
Object.entries(groupedUsers) Object.entries(groupedUsers)
.filter(([groupName]) => !classroomTransferState.classroomsToCreate.includes(groupName)) .filter(([groupName]) =>
!classroomTransferState.validClassrooms.includes(groupName) ||
existingGroupsMap[groupName]
)
); );
let updatePromises: Promise<any>[] = []; 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 existingGroupsWithIds = Object.entries(allExistingUsers).reduce((acc, [groupName, users]) => {
const { groups: groupNameToId, users: userEmailToId } = await axios.post('/api/groups?op=getIds', { const groupId = groupNameToId[groupName];
names: Object.keys(existingGroupUsers), if (groupId) {
userEmails: Object.values(existingGroupUsers).flat().map(user => user.email) acc[groupId] = users;
}).then(response => response.data); }
const existingGroupsWithIds = Object.entries(existingGroupUsers).reduce((acc, [groupName, users]) => {
acc[groupNameToId[groupName]] = users;
return acc; return acc;
}, {} as Record<string, any[]>); }, {} as Record<string, UserImport[]>);
updatePromises = Object.entries(existingGroupsWithIds).map(([groupId, users]) => { updatePromises = Object.entries(existingGroupsWithIds).map(([groupId, users]) => {
const userIds = users.map(user => userEmailToId[user.email]); const userIds = users.map(user => userEmailToId[user.email]);
@@ -506,13 +524,14 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
}); });
}); });
} }
await Promise.all([ await Promise.all([
...createGroupPromises, ...createGroupPromises,
...updatePromises ...updatePromises
]); ]);
toast.success(`Successfully assigned all ${classroomTransferState.imports.length} user(s)!`); toast.success(`Successfully assigned all ${classroomTransferState.imports.length} user(s)!`);
router.replace("/classrooms");
onFinish(); onFinish();
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@@ -19,6 +19,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
} }
else if (req.method === 'POST') { else if (req.method === 'POST') {
switch (op) { switch (op) {
case 'existantGroupIds':
res.status(200).json(await existantGroupIds(req.body.names));
break;
case 'crossRefOwnership': case 'crossRefOwnership':
res.status(200).json(await crossRefOwnership(req.body)); res.status(200).json(await crossRefOwnership(req.body));
break; break;
@@ -41,7 +44,7 @@ async function crossRefOwnership(body: any): Promise<string[]> {
const { userId, classrooms, entity } = body; const { userId, classrooms, entity } = body;
const existingClassrooms = await db.collection('groups') const existingClassrooms = await db.collection('groups')
.find({ .find({
name: { $in: classrooms }, name: { $in: classrooms },
admin: { $ne: userId } admin: { $ne: userId }
}) })
@@ -61,7 +64,7 @@ async function crossRefOwnership(body: any): Promise<string[]> {
const adminEntitiesMap = new Map( const adminEntitiesMap = new Map(
adminUsers.map(admin => [ adminUsers.map(admin => [
admin.id, admin.id,
admin.entities?.map((e: any) => e.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) { async function deletePriorEntitiesGroups(body: any) {
const { ids, entity } = body; const { ids, entity } = body;
if (!Array.isArray(ids) || ids.length === 0 || !entity) { if (!Array.isArray(ids) || ids.length === 0 || !entity) {
return; return;
} }
@@ -123,8 +125,33 @@ async function deletePriorEntitiesGroups(body: any) {
return; 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 } }, { participants: { $in: toDeleteUserIds } },
{ $pull: { participants: { $in: toDeleteUserIds } } } as any { $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>);
} }