ENCOA-309

This commit is contained in:
Carlos-Mesquita
2025-01-05 21:03:52 +00:00
parent 61e07dae95
commit 8f77f28aaa
5 changed files with 264 additions and 173 deletions

View File

@@ -22,10 +22,26 @@ import { EntityWithRoles } from "@/interfaces/entity";
const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/);
export interface DuplicateClassroom {
rowNumber: number;
email: string;
classroom: string;
}
export interface Mismatches {
email: string;
rows: number[];
mismatches: {
field: string;
values: any[];
}[];
}
export interface ClassroomTransferState {
stage: number;
parsedExcel: { rows?: any[]; errors?: any[] } | undefined;
duplicatedRows: { duplicates: ExcelUserDuplicatesMap, count: number } | undefined;
duplicatedRows: DuplicateClassroom[];
userMismatches: Mismatches[];
imports: UserImport[];
notFoundUsers: UserImport[];
otherEntityUsers: UserImport[];
@@ -43,7 +59,8 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
const [classroomTransferState, setClassroomTransferState] = useState<ClassroomTransferState>({
stage: 0,
parsedExcel: undefined,
duplicatedRows: undefined,
duplicatedRows: [],
userMismatches: [],
imports: [],
notFoundUsers: [],
otherEntityUsers: [],
@@ -169,79 +186,116 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
}, [filesContent]);
// Stage 1 - Excel Parsing
// - Find duplicate rows
// - Group rows by emails
// - Find duplicate rows (email + classroom)
// - Find email and other fields mismatch except classroom
// - Parsing errors
// - Set the data
useEffect(() => {
if (classroomTransferState.parsedExcel && classroomTransferState.parsedExcel.rows) {
const duplicates: ExcelUserDuplicatesMap = {
studentID: new Map(),
email: new Map(),
passport_id: new Map(),
phone: new Map()
};
const duplicateValues = new Set<string>();
const duplicateRowIndices = new Set<number>();
const errorRowIndices = new Set(
classroomTransferState.parsedExcel.errors?.map(error => error.row) || []
);
const emailClassroomMap = new Map<string, Map<string, number[]>>(); // email -> (group -> row numbers)
const emailRowMap = new Map<string, Map<number, any>>(); // email -> (row number -> row data)
// Group rows by email and assign row data
classroomTransferState.parsedExcel.rows.forEach((row, index) => {
if (!errorRowIndices.has(index + 2)) {
(Object.keys(duplicates) as Array<keyof ExcelUserDuplicatesMap>).forEach(field => {
if (row !== null) {
const value = row[field];
if (value) {
if (!duplicates[field].has(value)) {
duplicates[field].set(value, [index + 2]);
} else {
const existingRows = duplicates[field].get(value);
if (existingRows) {
existingRows.push(index + 2);
duplicateValues.add(value);
existingRows.forEach(rowNum => duplicateRowIndices.add(rowNum));
}
}
}
}
});
const rowNum = index + 2;
if (!errorRowIndices.has(rowNum) && row !== null) {
const email = row.email.toString().trim().toLowerCase();
const classroom = row.group;
if (!emailRowMap.has(email)) {
emailRowMap.set(email, new Map());
}
emailRowMap.get(email)!.set(rowNum, row);
if (!emailClassroomMap.has(email)) {
emailClassroomMap.set(email, new Map());
}
const classroomMap = emailClassroomMap.get(email)!;
if (!classroomMap.has(classroom)) {
classroomMap.set(classroom, []);
}
classroomMap.get(classroom)!.push(rowNum);
}
});
const infos = classroomTransferState.parsedExcel.rows
.map((row, index) => {
if (errorRowIndices.has(index + 2) || duplicateRowIndices.has(index + 2) || row === null) {
return undefined;
const duplicatedRows: DuplicateClassroom[] = [];
const userMismatches: Mismatches[] = [];
const validRows = [];
for (const [email, classroomMap] of emailClassroomMap) {
const rowDataMap = emailRowMap.get(email)!;
const allRowsForEmail = Array.from(rowDataMap.keys());
// Check for duplicates (same email + classroom)
for (const [classroom, rowNumbers] of classroomMap) {
if (rowNumbers.length > 1) {
rowNumbers.forEach(row => duplicatedRows.push({
rowNumber: row,
email,
classroom
}));
} else {
validRows.push(rowNumbers[0]);
}
const { firstName, lastName, studentID, passport_id, email, phone, group, country } = row;
if (!email || !EMAIL_REGEX.test(email.toString().trim())) {
return undefined;
}
// Check for mismatches in other fields
if (allRowsForEmail.length > 1) {
const fields = ['firstName', 'lastName', 'studentID', 'passport_id', 'phone'];
const mismatches: {field: string; values: any[]}[] = [];
fields.forEach(field => {
const uniqueValues = new Set(
allRowsForEmail.map(rowNum => rowDataMap.get(rowNum)![field])
);
if (uniqueValues.size > 1) {
mismatches.push({
field,
values: Array.from(uniqueValues)
});
}
});
if (mismatches.length > 0) {
userMismatches.push({
email,
rows: allRowsForEmail,
mismatches
});
}
return {
email: email.toString().trim().toLowerCase(),
name: `${firstName ?? ""} ${lastName ?? ""}`.trim(),
passport_id: passport_id?.toString().trim() || undefined,
groupName: group,
studentID,
demographicInformation: {
country: country?.countryCode,
passport_id: passport_id?.toString().trim() || undefined,
phone: phone.toString(),
},
entity: undefined,
type: undefined
} as UserImport;
})
.filter((item): item is UserImport => item !== undefined);
}
}
const imports = validRows
.map(rowNum => classroomTransferState.parsedExcel!.rows![rowNum - 2])
.filter((row): row is any => row !== null)
.map(row => ({
email: row.email.toString().trim().toLowerCase(),
name: `${row.firstName ?? ""} ${row.lastName ?? ""}`.trim(),
passport_id: row.passport_id?.toString().trim() || undefined,
groupName: row.group,
studentID: row.studentID,
demographicInformation: {
country: row.country?.countryCode,
passport_id: row.passport_id?.toString().trim() || undefined,
phone: row.phone.toString(),
},
entity: undefined,
type: undefined
} as UserImport));
// On import reset state except excel parsing
setClassroomTransferState((prev) => ({
...prev,
stage: 1,
duplicatedRows: { duplicates, count: duplicateRowIndices.size },
imports: infos,
duplicatedRows,
userMismatches,
imports,
notFoundUsers: [],
otherEntityUsers: [],
notOwnedClassrooms: [],
@@ -342,7 +396,8 @@ const StudentClassroomTransfer: React.FC<{ user: User; entities?: EntityWithRole
setClassroomTransferState({
stage: 0,
parsedExcel: undefined,
duplicatedRows: undefined,
duplicatedRows: [],
userMismatches: [],
imports: [],
notFoundUsers: [],
otherEntityUsers: [],