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

@@ -14,19 +14,28 @@ import UserTable from '../Tables/UserTable';
import ParseExcelErrors from './ExcelError';
import { errorsByRows } from '@/utils/excel.errors';
import { ClassroomTransferState } from '../Imports/StudentClassroomTransfer';
import { ExcelUserDuplicatesMap } from './User';
const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ state }) => {
const ClassroomImportSummary: React.FC<{ state: ClassroomTransferState }> = ({ state }) => {
const [showErrorsModal, setShowErrorsModal] = useState(false);
const [showDuplicatesModal, setShowDuplicatesModal] = useState(false);
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 ?
const errorCount = state.parsedExcel?.errors ?
Object.entries(errorsByRows(state.parsedExcel.errors)).length : 0;
const fieldMapper: { [key: string]: string } = {
"firstName": "First Name",
"lastName": "Last Name",
"studentID": "Student ID",
"phone": "Phone Number",
"passport_id": "Passport/National ID",
"country": "Country"
}
return (
<>
<Card>
@@ -120,11 +129,11 @@ const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ sta
</div>
)}
{state.duplicatedRows && state.duplicatedRows.count > 0 && (
{state.duplicatedRows.length > 0 && (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FaExclamationCircle className="h-5 w-5 text-yellow-500" />
<span>{state.duplicatedRows.count} duplicate entries in file</span>
<span>{state.duplicatedRows.length} duplicate entries in file</span>
</div>
<button
onClick={() => setShowDuplicatesModal(true)}
@@ -135,6 +144,21 @@ const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ sta
</div>
)}
{state.userMismatches.length > 0 && (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FaExclamationTriangle className="h-5 w-5 text-orange-500" />
<span>{state.userMismatches.length} users with mismatched information</span>
</div>
<button
onClick={() => setShowMismatchesModal(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 mismatches
</button>
</div>
)}
{errorCount > 0 && (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -151,73 +175,79 @@ const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ sta
)}
</div>
{((state.duplicatedRows?.count ?? 0) > 0 || errorCount > 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="flex items-start gap-4">
<div className="mt-1">
<FaExclamationTriangle className="h-5 w-5 text-red-400" />
</div>
<div className="flex-1 space-y-3">
<p className="font-medium text-gray-900">
The following will be excluded from transfer:
</p>
<ul className="space-y-4">
{state.notFoundUsers.length > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.notFoundUsers.length}</span> users not found
</div>
</li>
)}
{state.otherEntityUsers.length > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.otherEntityUsers.length}</span> users from different entities
</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(', ')}
{(state.duplicatedRows.length > 0 || state.userMismatches.length > 0 || errorCount > 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="flex items-start gap-4">
<div className="mt-1">
<FaExclamationTriangle className="h-5 w-5 text-red-400" />
</div>
<div className="flex-1 space-y-3">
<p className="font-medium text-gray-900">
The following will be excluded from transfer:
</p>
<ul className="space-y-4">
{state.notFoundUsers.length > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.notFoundUsers.length}</span> users not found
</div>
</div>
</li>
)}
{state.duplicatedRows && state.duplicatedRows.count > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.duplicatedRows.count}</span> duplicate entries
</div>
</li>
)}
{state.alreadyInClass && state.alreadyInClass.length > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.alreadyInClass.length}</span> users that are already assigned to the classroom
</div>
</li>
)}
{errorCount > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{errorCount}</span> rows with invalid information
</div>
</li>
)}
</ul>
</li>
)}
{state.otherEntityUsers.length > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.otherEntityUsers.length}</span> users from different entities
</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">
<span className="font-medium">{state.duplicatedRows.length}</span> duplicate entries
</div>
</li>
)}
{state.userMismatches.length > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.userMismatches.length}</span> users with mismatched information
</div>
</li>
)}
{state.alreadyInClass && state.alreadyInClass.length > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{state.alreadyInClass.length}</span> users that are already assigned to the classroom
</div>
</li>
)}
{errorCount > 0 && (
<li>
<div className="text-gray-700">
<span className="font-medium">{errorCount}</span> rows with invalid information
</div>
</li>
)}
</ul>
</div>
</div>
</div>
</div>
)}
)}
</CardContent>
</Card>
{/* Modals */}
<Modal isOpen={showErrorsModal} onClose={() => setShowErrorsModal(false)}>
<>
<div className="flex items-center gap-2 mb-6">
@@ -283,53 +313,59 @@ const ClassroomImportSummary: React.FC<{state: ClassroomTransferState}> = ({ sta
<FaExclamationCircle className="w-5 h-5 text-yellow-500" />
<h2 className="text-lg font-semibold text-gray-900">Duplicate Entries</h2>
</div>
{state.duplicatedRows && (
<div className="space-y-6">
{(Object.keys(state.duplicatedRows.duplicates) as Array<keyof ExcelUserDuplicatesMap>).map(field => {
const duplicates = Array.from(state.duplicatedRows!.duplicates[field].entries())
.filter((entry): entry is [string, number[]] => entry[1].length > 1);
if (duplicates.length === 0) return null;
return (
<div key={field} className="relative">
<div className="flex items-center gap-2 mb-3">
<h2 className="text-md font-medium text-gray-700">
{field} duplicates
</h2>
<span className="text-xs text-gray-500 ml-auto">
{duplicates.length} {duplicates.length === 1 ? 'duplicate' : 'duplicates'}
</span>
<div className="space-y-3">
{state.duplicatedRows.map((duplicate) => (
<div
key={`${duplicate.email}-${duplicate.classroom}-${duplicate.rowNumber}`}
className="group relative rounded-lg border border-gray-200 bg-gray-50 p-3 hover:bg-gray-100 transition-colors"
>
<div className="flex items-start justify-between">
<div>
<span className="font-medium text-gray-900">Row {duplicate.rowNumber}</span>
<div className="mt-1 text-sm text-gray-600">
Email: <span className="text-blue-600 font-medium">{duplicate.email}</span>
</div>
<div className="space-y-2">
{duplicates.map(([value, rows]) => (
<div
key={value}
className="group relative rounded-lg border border-gray-200 bg-gray-50 p-3 hover:bg-gray-100 transition-colors"
>
<div className="flex items-start justify-between">
<div>
<span className="font-medium text-gray-900">{value}</span>
<div className="mt-1 text-sm text-gray-600">
Appears in rows:
<span className="ml-1 text-blue-600 font-medium">
{rows.join(', ')}
</span>
</div>
</div>
<span className="text-xs text-gray-500">
{rows.length} occurrences
</span>
</div>
</div>
))}
<div className="mt-1 text-sm text-gray-600">
Classroom: <span className="text-blue-600 font-medium">{duplicate.classroom}</span>
</div>
</div>
);
})}
</div>
)}
</div>
</div>
))}
</div>
</>
</Modal>
<Modal isOpen={showMismatchesModal} onClose={() => setShowMismatchesModal(false)}>
<>
<div className="flex items-center gap-2 mb-6">
<FaExclamationTriangle className="w-5 h-5 text-orange-500" />
<h2 className="text-lg font-semibold text-gray-900">Mismatched User Information</h2>
</div>
<div className="space-y-6">
{state.userMismatches.map((mismatch) => (
<div
key={mismatch.email}
className="relative rounded-lg border border-gray-200 bg-gray-50 p-4"
>
<div className="mb-2 font-medium text-gray-900">
Email: {mismatch.email}
</div>
<div className="text-sm text-gray-600">
Rows: {mismatch.rows.join(', ')}
</div>
<div className="mt-3 space-y-2">
{mismatch.mismatches.map((field) => (
<div key={field.field} className="pl-4 border-l-2 border-orange-500">
<div className="font-medium text-gray-700">{fieldMapper[field.field]}:</div>
<div className="mt-1 text-sm text-gray-600">
Values: {field.values.join(', ')}
</div>
</div>
))}
</div>
</div>
))}
</div>
</>
</Modal>
</>

View File

@@ -116,7 +116,7 @@ const CodeGenImportSummary: React.FC<Props> = ({ infos, parsedExcel, duplicateRo
if (duplicates.length === 0) return null;
return (
<div key={field} className="ml-4 text-sm text-gray-600">
<span className="text-gray-500">{field}:</span> rows {
<span className="text-gray-500">{fieldMapper[field]}:</span> rows {
duplicates.map(([_, rows]) => rows.join(', ')).join('; ')
}
</div>

View File

@@ -162,7 +162,7 @@ const UserImportSummary: React.FC<Props> = ({ parsedExcel, newUsers, enlistedUse
if (duplicates.length === 0) return null;
return (
<div key={field} className="ml-4 text-sm text-gray-600">
<span className="text-gray-500">{field}:</span> rows {
<span className="text-gray-500">{fieldMapper[field]}:</span> rows {
duplicates.map(([_, rows]) => rows.join(', ')).join('; ')
}
</div>