Merged in feature/ExamGenRework (pull request #132)

Removed a non sense entity ownership classroom name check I added a while back, patched the same entity + same name + same admin query

Approved-by: Tiago Ribeiro
This commit is contained in:
carlos.mesquita
2025-01-06 21:21:05 +00:00
committed by Tiago Ribeiro
4 changed files with 17 additions and 166 deletions

View File

@@ -21,7 +21,6 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
const [showNotFoundModal, setShowNotFoundModal] = useState(false);
const [showOtherEntityModal, setShowOtherEntityModal] = useState(false);
const [showAlreadyInClassModal, setShowAlreadyInClassModal] = useState(false);
const [showNotOwnedModal, setShowNotOwnedModal] = useState(false);
const [showMismatchesModal, setShowMismatchesModal] = useState(false);
const errorCount = state.parsedExcel?.errors ?
@@ -114,21 +113,6 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
</div>
)}
{state.notOwnedClassrooms.length > 0 && (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FaLock className="h-5 w-5 text-red-500" />
<span>{`${state.notOwnedClassrooms.length} classroom${state.notOwnedClassrooms.length !== 1 ? 's' : ''} not owned`}</span>
</div>
<button
onClick={() => setShowNotOwnedModal(true)}
className="inline-flex items-center justify-center px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded-md transition-colors"
>
View details
</button>
</div>
)}
{state.duplicatedRows.length > 0 && (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -176,8 +160,7 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
</div>
{(state.duplicatedRows.length > 0 || state.userMismatches.length > 0 || errorCount > 0 ||
state.notFoundUsers.length > 0 || state.otherEntityUsers.length > 0 ||
state.notOwnedClassrooms.length > 0) && (
state.notFoundUsers.length > 0 || state.otherEntityUsers.length > 0) && (
<div className="mt-6 rounded-lg border border-red-100 bg-white p-6">
<div className="flex items-start gap-4">
<div className="mt-1">
@@ -202,16 +185,6 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
</div>
</li>
)}
{state.notOwnedClassrooms.length > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.notOwnedClassrooms.length}</span> classrooms not owned:
<div className="mt-1 ml-4 text-sm text-gray-600">
{state.notOwnedClassrooms.join(', ')}
</div>
</div>
</li>
)}
{state.duplicatedRows.length > 0 && (
<li>
<div className="text-gray-700">
@@ -288,25 +261,6 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
</>
</Modal>
<Modal isOpen={showNotOwnedModal} onClose={() => setShowNotOwnedModal(false)}>
<>
<div className="flex items-center gap-2 mb-6">
<FaLock className="w-5 h-5 text-red-500" />
<h2 className="text-lg font-semibold text-gray-900">Classrooms Not Owned</h2>
</div>
<div className="space-y-3">
{state.notOwnedClassrooms.map(classroom => (
<div
key={classroom}
className="flex justify-between items-center rounded-lg border border-gray-200 bg-gray-50 p-3"
>
<span className="text-gray-700">{classroom}</span>
</div>
))}
</div>
</>
</Modal>
<Modal isOpen={showDuplicatesModal} onClose={() => setShowDuplicatesModal(false)}>
<>
<div className="flex items-center gap-2 mb-6">

View File

@@ -4,7 +4,6 @@ import Modal from "../Modal";
import { useFilePicker } from "use-file-picker";
import readXlsxFile from "read-excel-file";
import countryCodes from "country-codes-list";
import { ExcelUserDuplicatesMap } from "../ImportSummaries/User";
import { UserImport } from "@/interfaces/IUserImport";
import axios from "axios";
import { toast } from "react-toastify";
@@ -47,8 +46,6 @@ export interface ClassroomTransferState {
notFoundUsers: UserImport[];
otherEntityUsers: UserImport[];
alreadyInClass: UserImport[];
notOwnedClassrooms: string[];
validClassrooms: string[];
}
@@ -66,8 +63,6 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
notFoundUsers: [],
otherEntityUsers: [],
alreadyInClass: [],
notOwnedClassrooms: [],
validClassrooms: []
})
const router = useRouter();
@@ -322,7 +317,8 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
email: info.email,
classroom: info.groupName
})),
entity
entity,
userId: user.id
});
const excludeEmails = new Set([
@@ -361,38 +357,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
crossRefUsers();
}
}, [classroomTransferState.imports, user.entities, classroomTransferState.stage, entity])
// Stage 3 - Classroom Filter
// - See if there are classrooms with same name but different admin
// - Find which new classrooms need to be created
useEffect(() => {
const crossRefClassrooms = async () => {
const classrooms = Array.from(new Set(classroomTransferState.imports.map((i) => i.groupName)));
try {
const { data: notOwnedClassroomsSameName } = await axios.post("/api/groups/controller?op=crossRefOwnership", {
userId: user.id,
classrooms
});
setClassroomTransferState((prev) => ({
...prev,
stage: 3,
notOwnedClassrooms: notOwnedClassroomsSameName,
validClassrooms: Array.from(classrooms).filter(
(name) => !new Set(notOwnedClassroomsSameName).has(name)
)
}))
} catch (error) {
toast.error("Something went wrong, please try again later!");
}
};
if (classroomTransferState.imports.length > 0 && classroomTransferState.stage === 2) {
crossRefClassrooms();
}
}, [classroomTransferState.imports, classroomTransferState.stage, user.id, entity])
}, [classroomTransferState.imports, user.entities, classroomTransferState.stage, entity, user.id])
const clearAndReset = () => {
setIsLoading(false);
@@ -405,8 +370,6 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
notFoundUsers: [],
otherEntityUsers: [],
alreadyInClass: [],
notOwnedClassrooms: [],
validClassrooms: []
});
clear();
};
@@ -425,6 +388,8 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
try {
setIsLoading(true);
const classrooms = Array.from(new Set(classroomTransferState.imports.map((i) => i.groupName)));
const getIds = async () => {
try {
const { data: emailIdMap } = await axios.post("/api/users/controller?op=getIds", {
@@ -458,7 +423,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
});
const { data: existingGroupsMap } = await axios.post("/api/groups/controller?op=existantGroupIds", {
names: classroomTransferState.validClassrooms
names: classrooms
});
const groupedUsers = imports.reduce((acc, user) => {
@@ -475,7 +440,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
const newGroupUsers = Object.fromEntries(
Object.entries(groupedUsers)
.filter(([groupName]) =>
classroomTransferState.validClassrooms.includes(groupName) &&
classrooms.includes(groupName) &&
!existingGroupsMap[groupName]
)
);
@@ -496,7 +461,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
const allExistingUsers = Object.fromEntries(
Object.entries(groupedUsers)
.filter(([groupName]) =>
!classroomTransferState.validClassrooms.includes(groupName) ||
!classrooms.includes(groupName) ||
existingGroupsMap[groupName]
)
);

View File

@@ -22,9 +22,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
case 'existantGroupIds':
res.status(200).json(await existantGroupIds(req.body.names));
break;
case 'crossRefOwnership':
res.status(200).json(await crossRefOwnership(req.body));
break;
case 'getIds':
res.status(200).json(await getIds(req.body));
break;
@@ -40,45 +37,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
}
async function crossRefOwnership(body: any): Promise<string[]> {
const { userId, classrooms, entity } = body;
const existingClassrooms = await db.collection('groups')
.find({
name: { $in: classrooms },
admin: { $ne: userId }
})
.project({ name: 1, admin: 1, _id: 0 })
.toArray();
if (existingClassrooms.length === 0) {
return [];
}
const adminUsers = await db.collection('users')
.find({
id: { $in: existingClassrooms.map(classroom => classroom.admin) }
})
.project({ id: 1, entities: 1, _id: 0 })
.toArray();
const adminEntitiesMap = new Map(
adminUsers.map(admin => [
admin.id,
admin.entities?.map((e: any) => e.id) || []
])
);
return Array.from(new Set(
existingClassrooms
.filter(classroom => {
const adminEntities = adminEntitiesMap.get(classroom.admin) || [];
return adminEntities.includes(entity);
})
.map(classroom => classroom.name)
));
}
async function getIds(body: any): Promise<Record<string, string>> {
const { names, userEmails } = body;

View File

@@ -169,7 +169,11 @@ async function entityCheck(body: Record<string, any>): Promise<Array<{
}
async function crossRefClassrooms(body: any) {
const { sets, entity } = body as { sets: { email: string, classroom: string }[], entity: string };
const { sets, entity, userId } = body as {
sets: { email: string, classroom: string }[],
entity: string,
userId: string
};
const pipeline = [
// Match users with the provided emails
@@ -189,6 +193,8 @@ async function crossRefClassrooms(body: any) {
$expr: {
$and: [
{ $in: ['$$userId', '$participants'] },
{ $eq: ['$admin', userId] },
{ $eq: ['$entity', entity] },
{
$let: {
vars: {
@@ -210,38 +216,6 @@ async function crossRefClassrooms(body: any) {
]
}
}
},
// Lookup admin's entities
{
$lookup: {
from: 'users',
let: { adminId: '$admin' },
pipeline: [
{
$match: {
$expr: { $eq: ['$id', '$$adminId'] }
}
}
],
as: 'adminInfo'
}
},
// Filter where admin has the target entity
{
$match: {
$expr: {
$in: [
entity,
{
$map: {
input: { $arrayElemAt: ['$adminInfo.entities', 0] },
as: 'entityObj',
in: '$$entityObj.id'
}
}
]
}
}
}
],
as: 'matchingGroups'