ENCOA-309
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user