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:
@@ -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">
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -169,8 +169,12 @@ 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'
|
||||
|
||||
Reference in New Issue
Block a user