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 [showNotFoundModal, setShowNotFoundModal] = useState(false);
|
||||||
const [showOtherEntityModal, setShowOtherEntityModal] = useState(false);
|
const [showOtherEntityModal, setShowOtherEntityModal] = useState(false);
|
||||||
const [showAlreadyInClassModal, setShowAlreadyInClassModal] = useState(false);
|
const [showAlreadyInClassModal, setShowAlreadyInClassModal] = useState(false);
|
||||||
const [showNotOwnedModal, setShowNotOwnedModal] = useState(false);
|
|
||||||
const [showMismatchesModal, setShowMismatchesModal] = useState(false);
|
const [showMismatchesModal, setShowMismatchesModal] = useState(false);
|
||||||
|
|
||||||
const errorCount = state.parsedExcel?.errors ?
|
const errorCount = state.parsedExcel?.errors ?
|
||||||
@@ -114,21 +113,6 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
|
|||||||
</div>
|
</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 && (
|
{state.duplicatedRows.length > 0 && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -176,8 +160,7 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(state.duplicatedRows.length > 0 || state.userMismatches.length > 0 || errorCount > 0 ||
|
{(state.duplicatedRows.length > 0 || state.userMismatches.length > 0 || errorCount > 0 ||
|
||||||
state.notFoundUsers.length > 0 || state.otherEntityUsers.length > 0 ||
|
state.notFoundUsers.length > 0 || state.otherEntityUsers.length > 0) && (
|
||||||
state.notOwnedClassrooms.length > 0) && (
|
|
||||||
<div className="mt-6 rounded-lg border border-red-100 bg-white p-6">
|
<div className="mt-6 rounded-lg border border-red-100 bg-white p-6">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
@@ -202,16 +185,6 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</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 && (
|
{state.duplicatedRows.length > 0 && (
|
||||||
<li>
|
<li>
|
||||||
<div className="text-gray-700">
|
<div className="text-gray-700">
|
||||||
@@ -288,25 +261,6 @@ const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ s
|
|||||||
</>
|
</>
|
||||||
</Modal>
|
</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)}>
|
<Modal isOpen={showDuplicatesModal} onClose={() => setShowDuplicatesModal(false)}>
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2 mb-6">
|
<div className="flex items-center gap-2 mb-6">
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import Modal from "../Modal";
|
|||||||
import { useFilePicker } from "use-file-picker";
|
import { useFilePicker } from "use-file-picker";
|
||||||
import readXlsxFile from "read-excel-file";
|
import readXlsxFile from "read-excel-file";
|
||||||
import countryCodes from "country-codes-list";
|
import countryCodes from "country-codes-list";
|
||||||
import { ExcelUserDuplicatesMap } from "../ImportSummaries/User";
|
|
||||||
import { UserImport } from "@/interfaces/IUserImport";
|
import { UserImport } from "@/interfaces/IUserImport";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -47,8 +46,6 @@ export interface ClassroomTransferState {
|
|||||||
notFoundUsers: UserImport[];
|
notFoundUsers: UserImport[];
|
||||||
otherEntityUsers: UserImport[];
|
otherEntityUsers: UserImport[];
|
||||||
alreadyInClass: UserImport[];
|
alreadyInClass: UserImport[];
|
||||||
notOwnedClassrooms: string[];
|
|
||||||
validClassrooms: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -66,8 +63,6 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
notFoundUsers: [],
|
notFoundUsers: [],
|
||||||
otherEntityUsers: [],
|
otherEntityUsers: [],
|
||||||
alreadyInClass: [],
|
alreadyInClass: [],
|
||||||
notOwnedClassrooms: [],
|
|
||||||
validClassrooms: []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -322,7 +317,8 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
email: info.email,
|
email: info.email,
|
||||||
classroom: info.groupName
|
classroom: info.groupName
|
||||||
})),
|
})),
|
||||||
entity
|
entity,
|
||||||
|
userId: user.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const excludeEmails = new Set([
|
const excludeEmails = new Set([
|
||||||
@@ -361,38 +357,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
crossRefUsers();
|
crossRefUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [classroomTransferState.imports, user.entities, classroomTransferState.stage, entity])
|
}, [classroomTransferState.imports, user.entities, classroomTransferState.stage, entity, user.id])
|
||||||
|
|
||||||
// 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])
|
|
||||||
|
|
||||||
|
|
||||||
const clearAndReset = () => {
|
const clearAndReset = () => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -405,8 +370,6 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
notFoundUsers: [],
|
notFoundUsers: [],
|
||||||
otherEntityUsers: [],
|
otherEntityUsers: [],
|
||||||
alreadyInClass: [],
|
alreadyInClass: [],
|
||||||
notOwnedClassrooms: [],
|
|
||||||
validClassrooms: []
|
|
||||||
});
|
});
|
||||||
clear();
|
clear();
|
||||||
};
|
};
|
||||||
@@ -425,6 +388,8 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const classrooms = Array.from(new Set(classroomTransferState.imports.map((i) => i.groupName)));
|
||||||
|
|
||||||
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", {
|
||||||
@@ -458,7 +423,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: existingGroupsMap } = await axios.post("/api/groups/controller?op=existantGroupIds", {
|
const { data: existingGroupsMap } = await axios.post("/api/groups/controller?op=existantGroupIds", {
|
||||||
names: classroomTransferState.validClassrooms
|
names: classrooms
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupedUsers = imports.reduce((acc, user) => {
|
const groupedUsers = imports.reduce((acc, user) => {
|
||||||
@@ -475,7 +440,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
const newGroupUsers = Object.fromEntries(
|
const newGroupUsers = Object.fromEntries(
|
||||||
Object.entries(groupedUsers)
|
Object.entries(groupedUsers)
|
||||||
.filter(([groupName]) =>
|
.filter(([groupName]) =>
|
||||||
classroomTransferState.validClassrooms.includes(groupName) &&
|
classrooms.includes(groupName) &&
|
||||||
!existingGroupsMap[groupName]
|
!existingGroupsMap[groupName]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -496,7 +461,7 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
|
|||||||
const allExistingUsers = Object.fromEntries(
|
const allExistingUsers = Object.fromEntries(
|
||||||
Object.entries(groupedUsers)
|
Object.entries(groupedUsers)
|
||||||
.filter(([groupName]) =>
|
.filter(([groupName]) =>
|
||||||
!classroomTransferState.validClassrooms.includes(groupName) ||
|
!classrooms.includes(groupName) ||
|
||||||
existingGroupsMap[groupName]
|
existingGroupsMap[groupName]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
case 'existantGroupIds':
|
case 'existantGroupIds':
|
||||||
res.status(200).json(await existantGroupIds(req.body.names));
|
res.status(200).json(await existantGroupIds(req.body.names));
|
||||||
break;
|
break;
|
||||||
case 'crossRefOwnership':
|
|
||||||
res.status(200).json(await crossRefOwnership(req.body));
|
|
||||||
break;
|
|
||||||
case 'getIds':
|
case 'getIds':
|
||||||
res.status(200).json(await getIds(req.body));
|
res.status(200).json(await getIds(req.body));
|
||||||
break;
|
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>> {
|
async function getIds(body: any): Promise<Record<string, string>> {
|
||||||
const { names, userEmails } = body;
|
const { names, userEmails } = body;
|
||||||
|
|
||||||
|
|||||||
@@ -169,8 +169,12 @@ async function entityCheck(body: Record<string, any>): Promise<Array<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function crossRefClassrooms(body: any) {
|
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 = [
|
const pipeline = [
|
||||||
// Match users with the provided emails
|
// Match users with the provided emails
|
||||||
{
|
{
|
||||||
@@ -189,6 +193,8 @@ async function crossRefClassrooms(body: any) {
|
|||||||
$expr: {
|
$expr: {
|
||||||
$and: [
|
$and: [
|
||||||
{ $in: ['$$userId', '$participants'] },
|
{ $in: ['$$userId', '$participants'] },
|
||||||
|
{ $eq: ['$admin', userId] },
|
||||||
|
{ $eq: ['$entity', entity] },
|
||||||
{
|
{
|
||||||
$let: {
|
$let: {
|
||||||
vars: {
|
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'
|
as: 'matchingGroups'
|
||||||
|
|||||||
Reference in New Issue
Block a user