import bug fix
This commit is contained in:
parent
9d5ca6874f
commit
63159b4b5d
@ -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,276 +2668,236 @@ const CompanyProfile = () => {
|
|||||||
return profiles[deletingRow];
|
return profiles[deletingRow];
|
||||||
}, [deletingRow, profiles]);
|
}, [deletingRow, profiles]);
|
||||||
|
|
||||||
const handleImport = async () => {
|
// Enhanced Import Function with Validation
|
||||||
if (!file) {
|
const handleImport = async () => {
|
||||||
const errorMsg = "Please select a CSV file to import.";
|
if (!file) {
|
||||||
setImportError(errorMsg);
|
const errorMsg = "Please select a CSV file to import.";
|
||||||
setToastData({
|
setImportError(errorMsg);
|
||||||
message: errorMsg,
|
|
||||||
type: "error",
|
|
||||||
});
|
|
||||||
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 {
|
|
||||||
setImportLoading(true);
|
|
||||||
setImportError("");
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", file);
|
|
||||||
|
|
||||||
console.log('FormData entries before upload:');
|
|
||||||
for (let pair of formData.entries()) {
|
|
||||||
console.log(pair[0] + ': ', pair[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let response;
|
|
||||||
let successMessage = "Company profiles imported successfully!";
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('Attempting first upload...');
|
|
||||||
response = await uploadCompanyProfileCSV(formData);
|
|
||||||
successMessage = response.message || successMessage;
|
|
||||||
} catch (firstError) {
|
|
||||||
console.log('First upload attempt failed, trying alternative:', firstError);
|
|
||||||
try {
|
|
||||||
console.log('Attempting alternative upload...');
|
|
||||||
response = await uploadCompanyProfileCSVAlternative(formData);
|
|
||||||
successMessage = response.message || successMessage;
|
|
||||||
} catch (secondError) {
|
|
||||||
console.log('Alternative upload also failed:', secondError);
|
|
||||||
|
|
||||||
let detailedErrorMessage = "Upload failed. Please try again.";
|
|
||||||
let validationErrorCount = 1; // Default count
|
|
||||||
|
|
||||||
// Extract validation error count from response if available
|
|
||||||
if (secondError.response?.data) {
|
|
||||||
const responseData = secondError.response.data;
|
|
||||||
|
|
||||||
// Try to extract validation error count from different response structures
|
|
||||||
if (responseData.validation_errors) {
|
|
||||||
validationErrorCount = responseData.validation_errors.length || 1;
|
|
||||||
} else if (responseData.errors) {
|
|
||||||
validationErrorCount = responseData.errors.length || 1;
|
|
||||||
} else if (responseData.details && Array.isArray(responseData.details)) {
|
|
||||||
validationErrorCount = responseData.details.length || 1;
|
|
||||||
} else if (typeof responseData === 'string') {
|
|
||||||
// Try to extract count from error message
|
|
||||||
const countMatch = responseData.match(/(\d+)\s*validation errors?/i);
|
|
||||||
if (countMatch) {
|
|
||||||
validationErrorCount = parseInt(countMatch[1]) || 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (secondError.response?.data?.message) {
|
|
||||||
detailedErrorMessage = secondError.response.data.message;
|
|
||||||
} else if (secondError.message) {
|
|
||||||
if (secondError.message.includes("Request failed with status code")) {
|
|
||||||
if (secondError.response?.data) {
|
|
||||||
const responseData = secondError.response.data;
|
|
||||||
if (typeof responseData === 'string' && responseData.includes("Duplicate Establishment IDs")) {
|
|
||||||
detailedErrorMessage = responseData;
|
|
||||||
} else if (responseData.error) {
|
|
||||||
detailedErrorMessage = responseData.error;
|
|
||||||
} else if (responseData.details) {
|
|
||||||
detailedErrorMessage = responseData.details;
|
|
||||||
} else {
|
|
||||||
detailedErrorMessage = "Data already exists in the system. Please remove or update the duplicate entries and try again.";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
detailedErrorMessage = "Server error occurred during upload. Please try again.";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
detailedErrorMessage = secondError.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enhanced validation error detection with dynamic count
|
|
||||||
if (detailedErrorMessage.includes("No file uploaded") ||
|
|
||||||
detailedErrorMessage.includes("No file found")) {
|
|
||||||
detailedErrorMessage = "Please select a valid CSV file to upload.";
|
|
||||||
} else if (detailedErrorMessage.includes("token") ||
|
|
||||||
detailedErrorMessage.includes("auth") ||
|
|
||||||
detailedErrorMessage.includes("Authentication")) {
|
|
||||||
detailedErrorMessage = "Authentication failed. Please check your login credentials.";
|
|
||||||
} else if (detailedErrorMessage.includes("400") ||
|
|
||||||
detailedErrorMessage.includes("Bad Request")) {
|
|
||||||
detailedErrorMessage = "Invalid file format or missing required data. Please check your CSV file.";
|
|
||||||
} else if (detailedErrorMessage.includes("Duplicate Establishment IDs") ||
|
|
||||||
detailedErrorMessage.includes("duplicate") ||
|
|
||||||
detailedErrorMessage.includes("already exist")) {
|
|
||||||
detailedErrorMessage = "Duplicate Establishment IDs found (in file or already exist in system). Resolve and re-upload.";
|
|
||||||
} else if (detailedErrorMessage.includes("validation error") ||
|
|
||||||
detailedErrorMessage.includes("validation errors") ||
|
|
||||||
detailedErrorMessage.includes("No establishments imported") ||
|
|
||||||
detailedErrorMessage.includes("HS Codes do not exist") ||
|
|
||||||
detailedErrorMessage.includes("HS Code")) {
|
|
||||||
// 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.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(detailedErrorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the response indicates validation errors
|
|
||||||
if (response && (response.message?.includes("validation error") ||
|
|
||||||
response.message?.includes("validation errors") ||
|
|
||||||
response.message?.includes("No establishments imported") ||
|
|
||||||
response.message?.includes("HS Codes do not exist") ||
|
|
||||||
response.message?.includes("HS Code"))) {
|
|
||||||
|
|
||||||
let validationErrorCount = 1;
|
|
||||||
// Extract count from response if available
|
|
||||||
if (response.validation_errors) {
|
|
||||||
validationErrorCount = response.validation_errors.length || 1;
|
|
||||||
} else if (response.errors) {
|
|
||||||
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.`;
|
|
||||||
|
|
||||||
// Show validation error in red
|
|
||||||
setImportError(validationErrorMessage);
|
|
||||||
setToastData({
|
setToastData({
|
||||||
message: validationErrorMessage,
|
message: errorMsg,
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
throw new Error(validationErrorMessage);
|
throw new Error(errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
setToastData({
|
|
||||||
message: successMessage,
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
|
|
||||||
closeImportModal();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setImportLoading(true);
|
||||||
const res = await fetchEstablishments();
|
setImportError("");
|
||||||
const payload = res?.data ?? res;
|
|
||||||
const records = Array.isArray(payload?.data)
|
|
||||||
? payload.data
|
|
||||||
: Array.isArray(payload)
|
|
||||||
? payload
|
|
||||||
: [];
|
|
||||||
|
|
||||||
setProfiles(records.map(mapApiEstablishmentToProfile));
|
// Re-validate file before upload
|
||||||
setTotalItems(records.length);
|
await validateFile(file);
|
||||||
} catch (refreshErr) {
|
|
||||||
console.log("Error refreshing data:", refreshErr);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
} catch (error) {
|
console.log('FormData entries before upload:');
|
||||||
console.error("Error importing CSV:", error);
|
for (let pair of formData.entries()) {
|
||||||
|
console.log(pair[0] + ': ', pair[1]);
|
||||||
|
}
|
||||||
|
|
||||||
let errorMessage = error.message || "Upload failed. Please try again.";
|
let response;
|
||||||
|
let successMessage = "Company profiles imported successfully!";
|
||||||
|
|
||||||
if (errorMessage.includes("Request failed with status code")) {
|
try {
|
||||||
const parts = errorMessage.split("Request failed with status code");
|
console.log('Attempting first upload...');
|
||||||
if (parts.length > 1) {
|
response = await uploadCompanyProfileCSV(formData);
|
||||||
const actualError = parts[1].replace(/^\d+\s*/, '').trim();
|
successMessage = response.message || successMessage;
|
||||||
if (actualError && actualError.length > 0) {
|
} catch (firstError) {
|
||||||
errorMessage = actualError;
|
console.log('First upload attempt failed, trying alternative:', firstError);
|
||||||
} else {
|
try {
|
||||||
errorMessage = "Server error occurred. Please try again.";
|
console.log('Attempting alternative upload...');
|
||||||
|
response = await uploadCompanyProfileCSVAlternative(formData);
|
||||||
|
successMessage = response.message || successMessage;
|
||||||
|
} catch (secondError) {
|
||||||
|
console.log('Alternative upload also failed:', secondError);
|
||||||
|
|
||||||
|
let detailedErrorMessage = "Upload failed. Please try again.";
|
||||||
|
let validationErrorCount = 1; // Default count
|
||||||
|
|
||||||
|
// Extract validation error count from response if available
|
||||||
|
if (secondError.response?.data) {
|
||||||
|
const responseData = secondError.response.data;
|
||||||
|
|
||||||
|
// Try to extract validation error count from different response structures
|
||||||
|
if (responseData.validation_errors) {
|
||||||
|
validationErrorCount = responseData.validation_errors.length || 1;
|
||||||
|
} else if (responseData.errors) {
|
||||||
|
validationErrorCount = responseData.errors.length || 1;
|
||||||
|
} else if (responseData.details && Array.isArray(responseData.details)) {
|
||||||
|
validationErrorCount = responseData.details.length || 1;
|
||||||
|
} else if (typeof responseData === 'string') {
|
||||||
|
// Try to extract count from error message
|
||||||
|
const countMatch = responseData.match(/(\d+)\s*validation errors?/i);
|
||||||
|
if (countMatch) {
|
||||||
|
validationErrorCount = parseInt(countMatch[1]) || 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secondError.response?.data?.message) {
|
||||||
|
detailedErrorMessage = secondError.response.data.message;
|
||||||
|
} else if (secondError.message) {
|
||||||
|
if (secondError.message.includes("Request failed with status code")) {
|
||||||
|
if (secondError.response?.data) {
|
||||||
|
const responseData = secondError.response.data;
|
||||||
|
if (typeof responseData === 'string' && responseData.includes("Duplicate Establishment IDs")) {
|
||||||
|
detailedErrorMessage = responseData;
|
||||||
|
} else if (responseData.error) {
|
||||||
|
detailedErrorMessage = responseData.error;
|
||||||
|
} else if (responseData.details) {
|
||||||
|
detailedErrorMessage = responseData.details;
|
||||||
|
} else {
|
||||||
|
detailedErrorMessage = "Data already exists in the system. Please remove or update the duplicate entries and try again.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
detailedErrorMessage = "Server error occurred during upload. Please try again.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
detailedErrorMessage = secondError.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced validation error detection with dynamic count
|
||||||
|
if (detailedErrorMessage.includes("No file uploaded") ||
|
||||||
|
detailedErrorMessage.includes("No file found")) {
|
||||||
|
detailedErrorMessage = "Please select a valid CSV file to upload.";
|
||||||
|
} else if (detailedErrorMessage.includes("token") ||
|
||||||
|
detailedErrorMessage.includes("auth") ||
|
||||||
|
detailedErrorMessage.includes("Authentication")) {
|
||||||
|
detailedErrorMessage = "Authentication failed. Please check your login credentials.";
|
||||||
|
} else if (detailedErrorMessage.includes("400") ||
|
||||||
|
detailedErrorMessage.includes("Bad Request")) {
|
||||||
|
detailedErrorMessage = "Invalid file format or missing required data. Please check your CSV file.";
|
||||||
|
} else if (detailedErrorMessage.includes("Duplicate Establishment IDs") ||
|
||||||
|
detailedErrorMessage.includes("duplicate") ||
|
||||||
|
detailedErrorMessage.includes("already exist")) {
|
||||||
|
detailedErrorMessage = "Duplicate Establishment IDs found (in file or already exist in system). Resolve and re-upload.";
|
||||||
|
} else if (detailedErrorMessage.includes("validation error") ||
|
||||||
|
detailedErrorMessage.includes("validation errors") ||
|
||||||
|
detailedErrorMessage.includes("No establishments imported") ||
|
||||||
|
detailedErrorMessage.includes("HS Codes do not exist") ||
|
||||||
|
detailedErrorMessage.includes("HS Code")) {
|
||||||
|
// 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.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(detailedErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Enhanced error message handling for validation errors with dynamic count
|
// Check if the response indicates validation errors
|
||||||
if (errorMessage.includes("expired") || errorMessage.includes("Authentication")) {
|
if (response && (response.message?.includes("validation error") ||
|
||||||
errorMessage = "Authentication failed. Please check your login credentials.";
|
response.message?.includes("validation errors") ||
|
||||||
}
|
response.message?.includes("No establishments imported") ||
|
||||||
|
response.message?.includes("HS Codes do not exist") ||
|
||||||
if (errorMessage.includes("Duplicate Establishment IDs") ||
|
response.message?.includes("HS Code"))) {
|
||||||
errorMessage.includes("duplicate") ||
|
|
||||||
errorMessage.includes("already exist")) {
|
let validationErrorCount = 1;
|
||||||
errorMessage = "Duplicate Establishment IDs found (in file or already exist in system). Resolve and re-upload.";
|
// Extract count from response if available
|
||||||
}
|
if (response.validation_errors) {
|
||||||
|
validationErrorCount = response.validation_errors.length || 1;
|
||||||
if (errorMessage.includes("validation error") ||
|
} else if (response.errors) {
|
||||||
errorMessage.includes("validation errors") ||
|
validationErrorCount = response.errors.length || 1;
|
||||||
errorMessage.includes("No establishments imported") ||
|
}
|
||||||
errorMessage.includes("HS Codes do not exist") ||
|
|
||||||
errorMessage.includes("HS Code")) {
|
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.`;
|
||||||
|
|
||||||
let validationErrorCount = 1;
|
// Show validation error in red
|
||||||
// Try to extract count from existing error message
|
setImportError(validationErrorMessage);
|
||||||
const countMatch = errorMessage.match(/(\d+)\s*validation errors?/i);
|
setToastData({
|
||||||
if (countMatch) {
|
message: validationErrorMessage,
|
||||||
validationErrorCount = parseInt(countMatch[1]) || 1;
|
type: "error",
|
||||||
|
});
|
||||||
|
throw new Error(validationErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
// Show success message
|
||||||
|
setToastData({
|
||||||
|
message: successMessage,
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
setToastData({
|
closeImportModal();
|
||||||
message: errorMessage,
|
setImportLoading(true); // Show loader while refreshing data
|
||||||
type: "error",
|
|
||||||
});
|
|
||||||
|
|
||||||
throw error;
|
try {
|
||||||
|
// Refresh the data
|
||||||
|
const res = await fetchEstablishments();
|
||||||
|
const payload = res?.data ?? res;
|
||||||
|
const records = Array.isArray(payload?.data)
|
||||||
|
? payload.data
|
||||||
|
: Array.isArray(payload)
|
||||||
|
? payload
|
||||||
|
: [];
|
||||||
|
|
||||||
} finally {
|
setProfiles(records.map(mapApiEstablishmentToProfile));
|
||||||
setImportLoading(false);
|
setTotalItems(records.length);
|
||||||
}
|
|
||||||
};
|
// Refresh the page immediately
|
||||||
|
window.location.reload();
|
||||||
const handleFileChange = (e) => {
|
} catch (refreshErr) {
|
||||||
if (e.target.files && e.target.files[0]) {
|
console.log("Error refreshing data:", refreshErr);
|
||||||
const selectedFile = e.target.files[0];
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
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) => {
|
return response;
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setDragActive(false);
|
|
||||||
|
|
||||||
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
|
} catch (error) {
|
||||||
const droppedFile = e.dataTransfer.files[0];
|
console.error("Error importing CSV:", error);
|
||||||
|
|
||||||
const isValidType = droppedFile.type === 'text/csv' ||
|
let errorMessage = error.message || "Upload failed. Please try again.";
|
||||||
droppedFile.name.toLowerCase().endsWith('.csv') ||
|
|
||||||
droppedFile.type === 'application/vnd.ms-excel';
|
if (errorMessage.includes("Request failed with status code")) {
|
||||||
|
const parts = errorMessage.split("Request failed with status code");
|
||||||
if (isValidType) {
|
if (parts.length > 1) {
|
||||||
setFile(droppedFile);
|
const actualError = parts[1].replace(/^\d+\s*/, '').trim();
|
||||||
setImportError('');
|
if (actualError && actualError.length > 0) {
|
||||||
} else {
|
errorMessage = actualError;
|
||||||
setImportError('Please upload a CSV file only. Supported formats: .csv');
|
} else {
|
||||||
|
errorMessage = "Server error occurred. Please try again.";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enhanced error message handling for validation errors with dynamic count
|
||||||
|
if (errorMessage.includes("expired") || errorMessage.includes("Authentication")) {
|
||||||
|
errorMessage = "Authentication failed. Please check your login credentials.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorMessage.includes("Duplicate Establishment IDs") ||
|
||||||
|
errorMessage.includes("duplicate") ||
|
||||||
|
errorMessage.includes("already exist")) {
|
||||||
|
errorMessage = "Duplicate Establishment IDs found (in file or already exist in system). Resolve and re-upload.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorMessage.includes("validation error") ||
|
||||||
|
errorMessage.includes("validation errors") ||
|
||||||
|
errorMessage.includes("No establishments imported") ||
|
||||||
|
errorMessage.includes("HS Codes do not exist") ||
|
||||||
|
errorMessage.includes("HS Code")) {
|
||||||
|
|
||||||
|
let validationErrorCount = 1;
|
||||||
|
// Try to extract count from existing error message
|
||||||
|
const countMatch = errorMessage.match(/(\d+)\s*validation errors?/i);
|
||||||
|
if (countMatch) {
|
||||||
|
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.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImportError(errorMessage);
|
||||||
|
|
||||||
|
setToastData({
|
||||||
|
message: errorMessage,
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
setImportLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -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]'
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@ -619,50 +619,52 @@ const QuarterlyWindows = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full" style={{ minWidth: '1200px' }}>
|
<div className="w-full overflow-x-auto">
|
||||||
<Table
|
<div className="min-w-[1200px]">
|
||||||
headers={headers}
|
<Table
|
||||||
columnWidths={columnwidth}
|
headers={headers}
|
||||||
rows={rows}
|
columnWidths={columnwidth}
|
||||||
renderCell={(value, rowIndex, colIndex) => {
|
rows={rows}
|
||||||
if (colIndex === headers.length - 1) {
|
renderCell={(value, rowIndex, colIndex) => {
|
||||||
return (
|
if (colIndex === headers.length - 1) {
|
||||||
<div className="flex items-center gap-2">
|
return (
|
||||||
<button
|
<div className="flex items-center gap-2">
|
||||||
type="button"
|
<button
|
||||||
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
type="button"
|
||||||
title="Edit"
|
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
||||||
aria-label="Edit Quarterly Survey"
|
title="Edit"
|
||||||
onClick={() => openEditModal(rowIndex)}
|
aria-label="Edit Quarterly Survey"
|
||||||
onMouseEnter={() => setHoveredEdit(rowIndex)}
|
onClick={() => openEditModal(rowIndex)}
|
||||||
onMouseLeave={() => setHoveredEdit(null)}
|
onMouseEnter={() => setHoveredEdit(rowIndex)}
|
||||||
>
|
onMouseLeave={() => setHoveredEdit(null)}
|
||||||
<img
|
>
|
||||||
src={hoveredEdit === rowIndex || editingRow === rowIndex ? pencilActiveSrc : pencilInactiveSrc}
|
<img
|
||||||
alt="Edit"
|
src={hoveredEdit === rowIndex || editingRow === rowIndex ? pencilActiveSrc : pencilInactiveSrc}
|
||||||
className="h-5 w-5"
|
alt="Edit"
|
||||||
/>
|
className="h-5 w-5"
|
||||||
</button>
|
/>
|
||||||
</div>
|
</button>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
return value;
|
}
|
||||||
}}
|
return value;
|
||||||
pagination={{
|
}}
|
||||||
currentPage,
|
pagination={{
|
||||||
onPageChange: (page) => {
|
currentPage,
|
||||||
setCurrentPage(page);
|
onPageChange: (page) => {
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
setCurrentPage(page);
|
||||||
},
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
pageSize,
|
},
|
||||||
totalItems: filteredRows.length,
|
pageSize,
|
||||||
pageSizeOptions: [10, 20, 50, 100],
|
totalItems: filteredRows.length,
|
||||||
onPageSizeChange: (size) => {
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
setPageSize(size);
|
onPageSizeChange: (size) => {
|
||||||
setCurrentPage(1);
|
setPageSize(size);
|
||||||
}
|
setCurrentPage(1);
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{modalMode && (
|
{modalMode && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user