import bug fix

This commit is contained in:
Malini 2025-11-19 10:28:34 +05:30
parent 9d5ca6874f
commit 63159b4b5d
2 changed files with 585 additions and 302 deletions

View File

@ -202,6 +202,258 @@ const createEmptyProfile = () => ({
createdById: '', createdById: '',
}); });
// Enhanced CSV validation function with detailed error reporting
const validateCSVFile = (file, existingEstablishments = []) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const csvText = e.target.result;
const lines = csvText.split('\n').filter(line => line.trim() !== '');
if (lines.length < 2) {
reject({
type: 'file_validation',
message: 'CSV file must contain at least a header and one data row',
details: []
});
return;
}
// Parse header
const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, ''));
// Check required headers
const requiredHeaders = ['Establishment Id', 'Factory Name', 'Email', 'Emirate', 'Total Employment'];
const missingHeaders = requiredHeaders.filter(header =>
!headers.some(h => h.toLowerCase() === header.toLowerCase())
);
if (missingHeaders.length > 0) {
reject({
type: 'header_validation',
message: `Missing required headers: ${missingHeaders.join(', ')}`,
details: missingHeaders.map(header => ({
header,
message: `Missing required header: ${header}`
}))
});
return;
}
const establishmentIdIndex = headers.findIndex(h =>
h.toLowerCase().includes('establishment') && h.toLowerCase().includes('id')
);
const factoryNameIndex = headers.findIndex(h =>
h.toLowerCase().includes('factory') && h.toLowerCase().includes('name')
);
const emailIndex = headers.findIndex(h => h.toLowerCase().includes('email'));
const emirateIndex = headers.findIndex(h => h.toLowerCase() === 'emirate');
const errors = [];
const establishmentIds = new Map(); // Track row numbers for each ID
const existingIds = new Set(existingEstablishments.map(est => est.establishmentId));
const validEmirates = ['Abu Dhabi', 'Dubai', 'Sharjah', 'Ajman', 'Umm Al Quwain', 'Ras Al Khaimah', 'Fujairah'];
// First pass: collect all establishment IDs to find duplicates
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
const rowNumber = i + 1;
const values = line.split(',').map(v => v.trim().replace(/"/g, ''));
// Skip empty rows
if (values.every(v => v === '')) continue;
if (establishmentIdIndex >= 0 && values[establishmentIdIndex]) {
const establishmentId = values[establishmentIdIndex];
if (establishmentIds.has(establishmentId)) {
establishmentIds.get(establishmentId).push(rowNumber);
} else {
establishmentIds.set(establishmentId, [rowNumber]);
}
}
}
// Second pass: validate each row
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
const rowNumber = i + 1;
const values = line.split(',').map(v => v.trim().replace(/"/g, ''));
// Skip empty rows
if (values.every(v => v === '')) continue;
const rowErrors = [];
// Check Establishment ID
if (establishmentIdIndex >= 0) {
const establishmentId = values[establishmentIdIndex] || '';
if (!establishmentId) {
rowErrors.push({
column: 'Establishment Id',
message: 'Establishment ID is required',
value: ''
});
} else {
// Check for duplicates within the file
const duplicateRows = establishmentIds.get(establishmentId) || [];
if (duplicateRows.length > 1) {
const otherRows = duplicateRows.filter(r => r !== rowNumber);
rowErrors.push({
column: 'Establishment Id',
message: `Duplicate ID found in rows: ${otherRows.join(', ')}`,
value: establishmentId,
isDuplicate: true,
duplicateRows: otherRows
});
}
// Check against existing establishments
if (existingIds.has(establishmentId)) {
rowErrors.push({
column: 'Establishment Id',
message: `Establishment ID '${establishmentId}' already exists in the system`,
value: establishmentId,
isSystemDuplicate: true
});
}
}
}
// Check Factory Name
if (factoryNameIndex >= 0 && !values[factoryNameIndex]) {
rowErrors.push({
column: 'Factory Name',
message: 'Factory Name is required',
value: ''
});
}
// Check Email
if (emailIndex >= 0) {
const email = values[emailIndex] || '';
if (!email) {
rowErrors.push({
column: 'Email',
message: 'Email is required',
value: ''
});
} else {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
rowErrors.push({
column: 'Email',
message: `Invalid email format: ${email}`,
value: email
});
}
}
}
// Check Emirate
if (emirateIndex >= 0) {
const emirate = values[emirateIndex] || '';
if (!emirate) {
rowErrors.push({
column: 'Emirate',
message: 'Emirate is required',
value: ''
});
} else if (!validEmirates.some(e => e.toLowerCase() === emirate.toLowerCase())) {
rowErrors.push({
column: 'Emirate',
message: `Invalid emirate: ${emirate}. Must be one of: ${validEmirates.join(', ')}`,
value: emirate
});
}
}
if (rowErrors.length > 0) {
errors.push({
row: rowNumber,
errors: rowErrors,
data: values
});
}
}
if (errors.length > 0) {
// Group errors by type for better reporting
const duplicateErrors = errors.flatMap(row =>
row.errors
.filter(e => e.isDuplicate || e.isSystemDuplicate)
.map(error => ({
...error,
row: row.row,
data: row.data
}))
);
const otherErrors = errors.flatMap(row =>
row.errors
.filter(e => !e.isDuplicate && !e.isSystemDuplicate)
.map(error => ({
...error,
row: row.row,
data: row.data
}))
);
// Create a structured error object
const errorObj = {
type: 'row_validation',
message: `Validation failed for ${errors.length} row(s)`,
details: {
totalErrors: errors.reduce((sum, row) => sum + row.errors.length, 0),
duplicateErrors,
otherErrors,
sampleError: errors[0].errors[0],
sampleRow: errors[0].row
}
};
// Customize message based on error types
if (duplicateErrors.length > 0 && otherErrors.length === 0) {
if (duplicateErrors.some(e => e.isSystemDuplicate)) {
errorObj.message = `Found ${duplicateErrors.length} duplicate Establishment ID(s) that already exist in the system`;
} else {
errorObj.message = `Found ${duplicateErrors.length} duplicate Establishment ID(s) in the file`;
}
} else if (otherErrors.length > 0 && duplicateErrors.length === 0) {
errorObj.message = `Found ${otherErrors.length} validation error(s) in the file`;
}
reject(errorObj);
} else {
resolve({
isValid: true,
totalRows: lines.length - 1,
headers
});
}
} catch (error) {
reject({
type: 'parse_error',
message: `Failed to parse CSV file: ${error.message}`,
details: []
});
}
};
reader.onerror = () => {
reject({
type: 'file_error',
message: 'Failed to read file',
details: []
});
};
reader.readAsText(file);
});
};
const CompanyProfile = () => { const CompanyProfile = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [profiles, setProfiles] = React.useState([]); const [profiles, setProfiles] = React.useState([]);
@ -256,6 +508,7 @@ const CompanyProfile = () => {
const [importError, setImportError] = React.useState(''); const [importError, setImportError] = React.useState('');
const fileInputRef = React.useRef(null); const fileInputRef = React.useRef(null);
const [toastData, setToastData] = React.useState(null); const [toastData, setToastData] = React.useState(null);
const [validationResults, setValidationResults] = React.useState(null);
// Load current user from session storage // Load current user from session storage
React.useEffect(() => { React.useEffect(() => {
@ -384,12 +637,13 @@ const CompanyProfile = () => {
[form, requiredFieldsByStep] [form, requiredFieldsByStep]
); );
// Fixed Import CSV Functions // Enhanced Import CSV Functions with Validation
const openImportModal = () => { const openImportModal = () => {
setImportModalOpen(true); setImportModalOpen(true);
setFile(null); setFile(null);
setImportError(''); setImportError('');
setDragActive(false); setDragActive(false);
setValidationResults(null);
}; };
const closeImportModal = () => { const closeImportModal = () => {
@ -397,6 +651,7 @@ const CompanyProfile = () => {
setFile(null); setFile(null);
setImportError(''); setImportError('');
setDragActive(false); setDragActive(false);
setValidationResults(null);
}; };
const handleDrag = (e) => { const handleDrag = (e) => {
@ -454,6 +709,75 @@ const CompanyProfile = () => {
} }
}, []); }, []);
// Enhanced file validation with CSV parsing
const validateFile = async (selectedFile) => {
if (!selectedFile) {
throw new Error("Please select a CSV file to import.");
}
if (selectedFile.size > 10 * 1024 * 1024) {
throw new Error("File size too large. Please select a file smaller than 10MB.");
}
const isValidType = selectedFile.type === 'text/csv' ||
selectedFile.name.toLowerCase().endsWith('.csv') ||
selectedFile.type === 'application/vnd.ms-excel';
if (!isValidType) {
throw new Error('Please upload a CSV file only. Supported formats: .csv');
}
// Validate CSV content
const existingEstablishmentIds = profiles.map(p => p.establishmentId).filter(id => id);
const validation = await validateCSVFile(selectedFile, existingEstablishmentIds);
return validation;
};
const handleFileChange = async (e) => {
if (e.target.files && e.target.files[0]) {
const selectedFile = e.target.files[0];
try {
setImportError('');
setValidationResults(null);
const validation = await validateFile(selectedFile);
setFile(selectedFile);
setValidationResults(validation);
} catch (error) {
setImportError(error.message);
setFile(null);
setValidationResults(null);
}
}
};
const handleDrop = async (e) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
const droppedFile = e.dataTransfer.files[0];
try {
setImportError('');
setValidationResults(null);
const validation = await validateFile(droppedFile);
setFile(droppedFile);
setValidationResults(validation);
} catch (error) {
setImportError(error.message);
setFile(null);
setValidationResults(null);
}
}
};
const renderStepContent = () => { const renderStepContent = () => {
switch (activeStep) { switch (activeStep) {
case 0: case 0:
@ -1000,10 +1324,7 @@ const CompanyProfile = () => {
setProductError(''); setProductError('');
setSaveError(''); setSaveError('');
setSelectedProducts(prev => { setSelectedProducts(prev => prev.filter(p => p.id !== product.id));
const updated = prev.filter(p => p.id !== product.id);
return updated;
});
// Add back to available products if it matches the search term or there's no search term // Add back to available products if it matches the search term or there's no search term
if (!productSearchTerm || if (!productSearchTerm ||
@ -2347,6 +2668,7 @@ const CompanyProfile = () => {
return profiles[deletingRow]; return profiles[deletingRow];
}, [deletingRow, profiles]); }, [deletingRow, profiles]);
// Enhanced Import Function with Validation
const handleImport = async () => { const handleImport = async () => {
if (!file) { if (!file) {
const errorMsg = "Please select a CSV file to import."; const errorMsg = "Please select a CSV file to import.";
@ -2358,20 +2680,13 @@ const CompanyProfile = () => {
throw new Error(errorMsg); throw new Error(errorMsg);
} }
if (file.size > 10 * 1024 * 1024) {
const errorMsg = "File size too large. Please select a file smaller than 10MB.";
setImportError(errorMsg);
setToastData({
message: errorMsg,
type: "error",
});
throw new Error(errorMsg);
}
try { try {
setImportLoading(true); setImportLoading(true);
setImportError(""); setImportError("");
// Re-validate file before upload
await validateFile(file);
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
@ -2463,7 +2778,7 @@ const CompanyProfile = () => {
detailedErrorMessage.includes("HS Codes do not exist") || detailedErrorMessage.includes("HS Codes do not exist") ||
detailedErrorMessage.includes("HS Code")) { detailedErrorMessage.includes("HS Code")) {
// Format validation error with dynamic count and HS code message // Format validation error with dynamic count and HS code message
detailedErrorMessage = `Import failed. ${validationErrorCount} HS Codes in your file do not exist in the Product Master. Please use the correct HS Code and try again.`; detailedErrorMessage = `Import failed. ${validationErrorCount} HS Codes in your file do not exist in the Product Master. Please use the correct HS Code and try again.`;
} }
throw new Error(detailedErrorMessage); throw new Error(detailedErrorMessage);
@ -2485,7 +2800,7 @@ detailedErrorMessage = `Import failed. ${validationErrorCount} HS Codes in your
validationErrorCount = response.errors.length || 1; validationErrorCount = response.errors.length || 1;
} }
const validationErrorMessage = `Import failed. ${validationErrorCount} HS Codes in your file do not exist in the Product Master. Please use the correct HS Code and try again.`; const validationErrorMessage = `Import failed. ${validationErrorCount} HS Codes in your file do not exist in the Product Master. Please use the correct HS Code and try again.`;
// Show validation error in red // Show validation error in red
setImportError(validationErrorMessage); setImportError(validationErrorMessage);
@ -2496,15 +2811,17 @@ const validationErrorMessage = `Import failed. ${validationErrorCount} HS Codes
throw new Error(validationErrorMessage); throw new Error(validationErrorMessage);
} }
// Show success message
setToastData({ setToastData({
message: successMessage, message: successMessage,
type: "success", type: "success",
}); });
closeImportModal(); closeImportModal();
setImportLoading(true); // Show loader while refreshing data
try { try {
setIsLoading(true); // Refresh the data
const res = await fetchEstablishments(); const res = await fetchEstablishments();
const payload = res?.data ?? res; const payload = res?.data ?? res;
const records = Array.isArray(payload?.data) const records = Array.isArray(payload?.data)
@ -2515,6 +2832,9 @@ const validationErrorMessage = `Import failed. ${validationErrorCount} HS Codes
setProfiles(records.map(mapApiEstablishmentToProfile)); setProfiles(records.map(mapApiEstablishmentToProfile));
setTotalItems(records.length); setTotalItems(records.length);
// Refresh the page immediately
window.location.reload();
} catch (refreshErr) { } catch (refreshErr) {
console.log("Error refreshing data:", refreshErr); console.log("Error refreshing data:", refreshErr);
} finally { } finally {
@ -2564,7 +2884,7 @@ const validationErrorMessage = `Import failed. ${validationErrorCount} HS Codes
validationErrorCount = parseInt(countMatch[1]) || 1; validationErrorCount = parseInt(countMatch[1]) || 1;
} }
errorMessage = `Import failed. ${validationErrorCount} HS Code${validationErrorCount > 1 ? "s" : ""} in your file ${validationErrorCount > 1 ? "do" : "does"} not exist in the Product Master. Please use the correct HS Code${validationErrorCount > 1 ? "s" : ""} and try again.`; errorMessage = `Import failed. ${validationErrorCount} HS Code${validationErrorCount > 1 ? "s" : ""} in your file ${validationErrorCount > 1 ? "do" : "does"} not exist in the Product Master. Please use the correct HS Code${validationErrorCount > 1 ? "s" : ""} and try again.`;
} }
setImportError(errorMessage); setImportError(errorMessage);
@ -2579,45 +2899,6 @@ errorMessage = `Import failed. ${validationErrorCount} HS Code${validationErrorC
} finally { } finally {
setImportLoading(false); setImportLoading(false);
} }
};
const handleFileChange = (e) => {
if (e.target.files && e.target.files[0]) {
const selectedFile = e.target.files[0];
const isValidType = selectedFile.type === 'text/csv' ||
selectedFile.name.toLowerCase().endsWith('.csv') ||
selectedFile.type === 'application/vnd.ms-excel';
if (isValidType) {
setFile(selectedFile);
setImportError('');
} else {
setImportError('Please upload a CSV file only. Supported formats: .csv');
setFile(null);
}
}
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
const droppedFile = e.dataTransfer.files[0];
const isValidType = droppedFile.type === 'text/csv' ||
droppedFile.name.toLowerCase().endsWith('.csv') ||
droppedFile.type === 'application/vnd.ms-excel';
if (isValidType) {
setFile(droppedFile);
setImportError('');
} else {
setImportError('Please upload a CSV file only. Supported formats: .csv');
}
}
}; };
return ( return (
@ -2675,7 +2956,7 @@ errorMessage = `Import failed. ${validationErrorCount} HS Code${validationErrorC
</h3> </h3>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="relative min-w-[260px]"> <div className="relative min-w-[150px]">
<input <input
type="text" type="text"
placeholder="Search By Establishment Name" placeholder="Search By Establishment Name"
@ -2714,7 +2995,7 @@ errorMessage = `Import failed. ${validationErrorCount} HS Code${validationErrorC
<button <button
type="button" type="button"
onClick={openImportModal} onClick={openImportModal}
className="h-10 px-4 rounded-[6px] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 bg-[#F7F7F7] text-[#232528] hover:bg-gray-50" className="h-10 px-2 rounded-[6px] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 bg-[#F7F7F7] text-[#232528] hover:bg-gray-50"
> >
<img src={uploadImportIconSrc} alt="Import" className="h-5 w-5" /> <img src={uploadImportIconSrc} alt="Import" className="h-5 w-5" />
<span className="font-medium">Import CSV</span> <span className="font-medium">Import CSV</span>
@ -3058,9 +3339,9 @@ errorMessage = `Import failed. ${validationErrorCount} HS Code${validationErrorC
<p className="text-sm text-gray-500 mt-1"> <p className="text-sm text-gray-500 mt-1">
Supports .csv files only (Max 10MB) Supports .csv files only (Max 10MB)
</p> </p>
{file && ( {file && validationResults && (
<p className="text-xs text-green-600 mt-1"> <p className="text-xs text-green-600 mt-1">
File selected and ready to import File validated successfully - {validationResults.totalRows} rows ready to import
</p> </p>
)} )}
</div> </div>
@ -3120,9 +3401,9 @@ errorMessage = `Import failed. ${validationErrorCount} HS Code${validationErrorC
<button <button
type="button" type="button"
onClick={handleImport} onClick={handleImport}
disabled={!file || importLoading} disabled={!file || importLoading || !validationResults}
className={`h-10 px-6 text-sm font-medium text-white rounded-md ${ className={`h-10 px-6 text-sm font-medium text-white rounded-md ${
!file || importLoading !file || importLoading || !validationResults
? 'bg-gray-300 cursor-not-allowed' ? 'bg-gray-300 cursor-not-allowed'
: 'bg-[#92722A] hover:bg-[#7a5f22]' : 'bg-[#92722A] hover:bg-[#7a5f22]'
}`} }`}

View File

@ -619,7 +619,8 @@ const QuarterlyWindows = () => {
</div> </div>
</div> </div>
<div className="w-full" style={{ minWidth: '1200px' }}> <div className="w-full overflow-x-auto">
<div className="min-w-[1200px]">
<Table <Table
headers={headers} headers={headers}
columnWidths={columnwidth} columnWidths={columnwidth}
@ -664,6 +665,7 @@ const QuarterlyWindows = () => {
}} }}
/> />
</div> </div>
</div>
{modalMode && ( {modalMode && (
<div className="fixed inset-0 z-50"> <div className="fixed inset-0 z-50">