Add new frontend - fix 25

This commit is contained in:
Urtzi Alfaro
2025-07-23 18:57:27 +02:00
parent e3a5256281
commit 37a1b5f833
4 changed files with 722 additions and 275 deletions

View File

@@ -207,24 +207,23 @@ const OnboardingPage = () => {
const tenant: TenantInfo = await api.tenant.registerBakery(bakeryData);
setCurrentTenantId(tenant.id);
showNotification('success', 'Panadería registrada', 'Información guardada correctamente.');
} else if (currentStep === 3) {
// FIXED: Sales upload step with proper validation handling
// ✅ UPDATED: Sales upload step with new schema handling
if (formData.salesFile) {
try {
// Validate if not already validated
let validation = uploadValidation;
if (!validation) {
validation = await api.data.validateSalesData(formData.salesFile);
validation = await api.data.validateSalesData(formData.salesFile, currentTenantId);
setUploadValidation(validation);
}
// ✅ FIXED: Check validation using correct field name is_valid
// ✅ UPDATED: Check validation using new schema
if (!validation.is_valid) {
const errorMessages = validation.errors.map(error =>
`${error.row ? `Fila ${error.row}: ` : ''}${
typeof error === 'string' ? error : error.message
}`
const errors = validation.errors || [];
const errorMessages = errors.map(error =>
`${error.row ? `Fila ${error.row}: ` : ''}${error.message}`
).join('; ');
showNotification('error', 'Datos inválidos',
@@ -234,11 +233,10 @@ const OnboardingPage = () => {
}
// Show warnings if any
if (validation.warnings.length > 0) {
const warningMessages = validation.warnings.map(warning =>
`${warning.row ? `Fila ${warning.row}: ` : ''}${
typeof warning === 'string' ? warning : warning.message
}`
const warnings = validation.warnings || [];
if (warnings.length > 0) {
const warningMessages = warnings.map(warning =>
`${warning.row ? `Fila ${warning.row}: ` : ''}${warning.message}`
).join('; ');
showNotification('warning', 'Advertencias encontradas',
@@ -248,8 +246,10 @@ const OnboardingPage = () => {
// Proceed with actual upload
const uploadResult = await api.data.uploadSalesHistory(
formData.salesFile,
{ tenant_id: currentTenantId }
currentTenantId
);
showNotification('success', 'Archivo subido',
`${uploadResult.records_processed} registros procesados exitosamente.`);
@@ -322,131 +322,183 @@ const OnboardingPage = () => {
}
};
// ✅ FIXED: Update handleFileUpload to use backend's schema
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
setFormData(prev => ({ ...prev, salesFile: file }));
setUploadValidation(null);
try {
setLoading(true);
console.log('Validating file:', file.name);
setFormData(prev => ({ ...prev, salesFile: file }));
setUploadValidation(null);
// Pass the current tenant ID to validation
const validation = await api.data.validateSalesData(file, currentTenantId);
if (!validation) {
throw new Error('No validation response received from server');
try {
setLoading(true);
console.log('Validating file:', file.name);
// Pass the current tenant ID to validation
const validation = await api.data.validateSalesData(file, currentTenantId);
if (!validation) {
throw new Error('No validation response received from server');
}
console.log('Validation result:', validation);
setUploadValidation(validation);
// ✅ FIXED: Use backend's response structure (both "valid" and "is_valid" supported)
const isValid = validation.is_valid !== undefined ? validation.is_valid : validation.valid;
if (isValid) {
const recordCount = validation.total_records || validation.recordCount || 'Algunos';
showNotification('success', 'Archivo válido',
`${recordCount} registros detectados.`);
} else if (validation.warnings && validation.warnings.length > 0 &&
(!validation.errors || validation.errors.length === 0)) {
showNotification('warning', 'Archivo con advertencias',
'El archivo es válido pero tiene algunas advertencias.');
} else {
const errorCount = validation.errors ? validation.errors.length : 0;
showNotification('error', 'Archivo con errores',
`Se encontraron ${errorCount} errores en el archivo.`);
}
} catch (error: any) {
console.error('Error validating file:', error);
let errorMessage = 'Error al validar el archivo';
if (error.response?.status === 422) {
errorMessage = 'Formato de archivo inválido';
} else if (error.response?.status === 400) {
errorMessage = 'El archivo no se puede procesar';
} else if (error.response?.status === 500) {
errorMessage = 'Error del servidor. Inténtalo más tarde.';
} else if (error.message) {
errorMessage = error.message;
}
showNotification('error', 'Error de validación', errorMessage);
// ✅ FIXED: Set validation state using a unified structure
setUploadValidation({
is_valid: false,
valid: false, // Backward compatibility
errors: [{ type: 'client_error', message: errorMessage }],
warnings: [],
total_records: 0,
valid_records: 0,
invalid_records: 0,
summary: {
status: 'error',
suggestions: ['Intenta con un archivo diferente']
}
});
} finally {
setLoading(false);
}
console.log('Validation result:', validation);
setUploadValidation(validation);
// ✅ FIXED: Use backend's "valid" field instead of "is_valid"
if (validation.valid) {
showNotification('success', 'Archivo válido',
`${validation.recordCount || 'Algunos'} registros detectados.`);
} else if (validation.warnings && validation.warnings.length > 0 &&
validation.errors && validation.errors.length === 0) {
showNotification('warning', 'Archivo con advertencias',
'El archivo es válido pero tiene algunas advertencias.');
} else {
const errorCount = validation.errors ? validation.errors.length : 0;
showNotification('error', 'Archivo con errores',
`Se encontraron ${errorCount} errores en el archivo.`);
}
} catch (error: any) {
console.error('Error validating file:', error);
let errorMessage = 'Error al validar el archivo';
if (error.response?.status === 422) {
errorMessage = 'Formato de archivo inválido';
} else if (error.response?.status === 400) {
errorMessage = 'El archivo no se puede procesar';
} else if (error.response?.status === 500) {
errorMessage = 'Error del servidor. Inténtalo más tarde.';
} else if (error.message) {
errorMessage = error.message;
}
showNotification('error', 'Error de validación', errorMessage);
// ✅ FIXED: Set validation state using backend's schema
setUploadValidation({
valid: false,
errors: [errorMessage],
warnings: [],
suggestions: ['Intenta con un archivo diferente']
});
} finally {
setLoading(false);
}
};
};
// Fixed validation display component
const renderValidationResult = () => {
if (!uploadValidation) return null;
// ✅ NEW: Use the updated schema fields
const isValid = uploadValidation.is_valid;
const totalRecords = uploadValidation.total_records || 0;
const validRecords = uploadValidation.valid_records || 0;
const invalidRecords = uploadValidation.invalid_records || 0;
const errors = uploadValidation.errors || [];
const warnings = uploadValidation.warnings || [];
const summary = uploadValidation.summary || { suggestions: [] };
return (
<div className={`border rounded-lg p-4 ${
uploadValidation.valid ? 'bg-green-50 border-green-200' : 'bg-red-50 border-red-200'
isValid ? 'bg-green-50 border-green-200' : 'bg-red-50 border-red-200'
}`}>
<div className="flex items-start">
{uploadValidation.valid ? (
{isValid ? (
<CheckIcon className="w-5 h-5 text-green-600 mt-0.5 mr-3" />
) : (
<XMarkIcon className="w-5 h-5 text-red-600 mt-0.5 mr-3" />
)}
<div className="flex-1">
<h4 className={`font-semibold ${
uploadValidation.valid ? 'text-green-800' : 'text-red-800'
isValid ? 'text-green-800' : 'text-red-800'
}`}>
{uploadValidation.valid ? 'Archivo válido' : 'Archivo con problemas'}
{isValid ? 'Archivo válido' : 'Archivo con problemas'}
</h4>
{/* ✅ FIXED: Display record count using backend's field names */}
{/* ✅ ENHANCED: Display comprehensive record information */}
<p className={`text-sm mt-1 ${
uploadValidation.valid ? 'text-green-700' : 'text-red-700'
isValid ? 'text-green-700' : 'text-red-700'
}`}>
{uploadValidation.recordCount || 0} registros encontrados
{uploadValidation.duplicates && uploadValidation.duplicates > 0 &&
`, ${uploadValidation.duplicates} duplicados`}
{totalRecords > 0 && (
<>
{totalRecords} registros encontrados
{validRecords > 0 && ` (${validRecords} válidos`}
{invalidRecords > 0 && `, ${invalidRecords} con errores)`}
{validRecords > 0 && invalidRecords === 0 && ')'}
</>
)}
{summary.file_size_mb && (
<span className="ml-2 text-xs opacity-75">
{summary.file_size_mb}MB
</span>
)}
</p>
{/* ✅ FIXED: Handle errors as string array (backend's current format) */}
{!uploadValidation.valid && uploadValidation.errors && uploadValidation.errors.length > 0 && (
{/* ✅ ENHANCED: Display structured errors */}
{errors.length > 0 && (
<div className="mt-2">
<p className="text-sm font-medium text-red-700 mb-1">Errores encontrados:</p>
<p className="text-sm font-medium text-red-700 mb-1">Errores:</p>
<ul className="text-sm text-red-700 space-y-1">
{uploadValidation.errors.map((error, idx) => (
<li key={idx}> {error}</li>
{errors.slice(0, 3).map((error, idx) => (
<li key={idx} className="flex items-start">
<span className="inline-block w-2 h-2 bg-red-500 rounded-full mt-2 mr-2 flex-shrink-0"></span>
<span>
{error.row && `Fila ${error.row}: `}
{error.message}
</span>
</li>
))}
{errors.length > 3 && (
<li className="text-red-600 italic">
... y {errors.length - 3} errores más
</li>
)}
</ul>
</div>
)}
{/* ✅ FIXED: Handle warnings as string array (backend's current format) */}
{uploadValidation.warnings && uploadValidation.warnings.length > 0 && (
{/* ✅ ENHANCED: Display structured warnings */}
{warnings.length > 0 && (
<div className="mt-2">
<p className="text-sm font-medium text-yellow-700 mb-1">Advertencias:</p>
<ul className="text-sm text-yellow-700 space-y-1">
{uploadValidation.warnings.map((warning, idx) => (
<li key={idx}> {warning}</li>
{warnings.slice(0, 2).map((warning, idx) => (
<li key={idx} className="flex items-start">
<span className="inline-block w-2 h-2 bg-yellow-500 rounded-full mt-2 mr-2 flex-shrink-0"></span>
<span>
{warning.row && `Fila ${warning.row}: `}
{warning.message}
</span>
</li>
))}
{warnings.length > 2 && (
<li className="text-yellow-600 italic">
... y {warnings.length - 2} advertencias más
</li>
)}
</ul>
</div>
)}
{/* ✅ FIXED: Handle backend's "suggestions" field */}
{uploadValidation.suggestions && uploadValidation.suggestions.length > 0 && (
{/* ✅ ENHANCED: Display suggestions from summary */}
{summary.suggestions && summary.suggestions.length > 0 && (
<div className="mt-2">
<p className="text-sm font-medium text-blue-700 mb-1">Sugerencias:</p>
<ul className="text-sm text-blue-700 space-y-1">
{uploadValidation.suggestions.map((suggestion, idx) => (
<li key={idx}> {suggestion}</li>
{summary.suggestions.map((suggestion, idx) => (
<li key={idx} className="flex items-start">
<span className="inline-block w-2 h-2 bg-blue-500 rounded-full mt-2 mr-2 flex-shrink-0"></span>
<span>{suggestion}</span>
</li>
))}
</ul>
</div>
@@ -455,7 +507,7 @@ const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
</div>
</div>
);
};
};
const renderStepIndicator = () => (
<div className="mb-12">