Fix new services implementation 2
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { ChevronLeft, ChevronRight, Upload, MapPin, Store, Factory, Check, Brain, Clock, CheckCircle, AlertTriangle, Loader, TrendingUp } from 'lucide-react';
|
||||
import { ChevronLeft, ChevronRight, Upload, MapPin, Store, Check, Brain, Clock, CheckCircle, AlertTriangle, Loader, TrendingUp } from 'lucide-react';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
import SimplifiedTrainingProgress from '../../components/SimplifiedTrainingProgress';
|
||||
@@ -25,7 +25,7 @@ interface OnboardingPageProps {
|
||||
interface BakeryData {
|
||||
name: string;
|
||||
address: string;
|
||||
businessType: 'individual' | 'central_workshop';
|
||||
businessType?: 'bakery' | 'coffee_shop' | 'pastry_shop' | 'restaurant'; // Will be auto-detected from sales data
|
||||
coordinates?: { lat: number; lng: number };
|
||||
products: string[];
|
||||
hasHistoricalData: boolean;
|
||||
@@ -51,7 +51,6 @@ const MADRID_PRODUCTS = [
|
||||
const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) => {
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [useSmartImport, setUseSmartImport] = useState(true); // New state for smart import
|
||||
const manualNavigation = useRef(false);
|
||||
|
||||
// Enhanced onboarding with progress tracking
|
||||
@@ -64,7 +63,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
const [bakeryData, setBakeryData] = useState<BakeryData>({
|
||||
name: '',
|
||||
address: '',
|
||||
businessType: 'individual',
|
||||
// businessType will be auto-detected during smart data import
|
||||
products: MADRID_PRODUCTS, // Automatically assign all products
|
||||
hasHistoricalData: false
|
||||
});
|
||||
@@ -95,14 +94,6 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
estimatedTimeRemaining: 0
|
||||
});
|
||||
|
||||
// Sales data validation state
|
||||
const [validationStatus, setValidationStatus] = useState<{
|
||||
status: 'idle' | 'validating' | 'valid' | 'invalid';
|
||||
message?: string;
|
||||
records?: number;
|
||||
}>({
|
||||
status: 'idle'
|
||||
});
|
||||
|
||||
// Initialize current step from onboarding progress (only when not manually navigating)
|
||||
useEffect(() => {
|
||||
@@ -292,8 +283,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
try {
|
||||
if (currentStep === 1) {
|
||||
await createBakeryAndTenant();
|
||||
} else if (currentStep === 2) {
|
||||
await uploadAndValidateSalesData();
|
||||
// Smart import step handles its own processing
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Error al procesar el paso. Por favor, inténtalo de nuevo.');
|
||||
@@ -317,7 +307,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
const tenantData: TenantCreate = {
|
||||
name: bakeryData.name,
|
||||
address: bakeryData.address,
|
||||
business_type: "individual",
|
||||
business_type: 'bakery', // Default value - will be automatically updated after AI analyzes sales data
|
||||
postal_code: "28010",
|
||||
phone: "+34655334455",
|
||||
coordinates: bakeryData.coordinates,
|
||||
@@ -337,7 +327,8 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
await completeStep('bakery_registered', {
|
||||
bakery_name: bakeryData.name,
|
||||
bakery_address: bakeryData.address,
|
||||
business_type: bakeryData.businessType,
|
||||
business_type: 'bakery', // Default - will be auto-detected from sales data
|
||||
auto_detect_business_type: true, // Flag indicating business type will be auto-detected
|
||||
tenant_id: currentTenantId, // Use the correct tenant ID
|
||||
user_id: user?.id
|
||||
});
|
||||
@@ -354,81 +345,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
}
|
||||
};
|
||||
|
||||
// Validate sales data without importing
|
||||
const validateSalesFile = async () => {
|
||||
if (!tenantId || !bakeryData.csvFile) {
|
||||
toast.error('Falta información del tenant o archivo');
|
||||
return;
|
||||
}
|
||||
|
||||
setValidationStatus({ status: 'validating' });
|
||||
|
||||
try {
|
||||
const validationResult = await validateSalesData(tenantId, bakeryData.csvFile);
|
||||
|
||||
if (validationResult.is_valid) {
|
||||
setValidationStatus({
|
||||
status: 'valid',
|
||||
message: validationResult.message,
|
||||
records: validationResult.details?.total_records || 0
|
||||
});
|
||||
toast.success('¡Archivo validado correctamente!');
|
||||
} else {
|
||||
setValidationStatus({
|
||||
status: 'invalid',
|
||||
message: validationResult.message
|
||||
});
|
||||
toast.error(`Error en validación: ${validationResult.message}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
setValidationStatus({
|
||||
status: 'invalid',
|
||||
message: 'Error al validar el archivo'
|
||||
});
|
||||
toast.error('Error al validar el archivo');
|
||||
}
|
||||
};
|
||||
|
||||
// Upload and validate sales data for step 2
|
||||
const uploadAndValidateSalesData = async () => {
|
||||
if (!tenantId || !bakeryData.csvFile) {
|
||||
throw new Error('Falta información del tenant o archivo');
|
||||
}
|
||||
|
||||
try {
|
||||
// If not already validated, validate first
|
||||
if (validationStatus.status !== 'valid') {
|
||||
const validationResult = await validateSalesData(tenantId, bakeryData.csvFile);
|
||||
|
||||
if (!validationResult.is_valid) {
|
||||
toast.error(`Error en validación: ${validationResult.message}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If validation passes, upload the data
|
||||
const uploadResult = await uploadSalesHistory(tenantId, bakeryData.csvFile);
|
||||
|
||||
// Mark step as completed
|
||||
await completeStep('sales_data_uploaded', {
|
||||
file_name: bakeryData.csvFile.name,
|
||||
file_size: bakeryData.csvFile.size,
|
||||
records_imported: uploadResult.records_imported || 0,
|
||||
validation_status: 'success',
|
||||
tenant_id: tenantId,
|
||||
user_id: user?.id
|
||||
});
|
||||
|
||||
toast.success(`Datos importados correctamente (${uploadResult.records_imported} registros)`);
|
||||
|
||||
// Automatically start training after successful upload
|
||||
await startTraining();
|
||||
} catch (error: any) {
|
||||
// Error uploading sales data
|
||||
const message = error?.response?.data?.detail || error.message || 'Error al subir datos de ventas';
|
||||
throw new Error(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to mark current step as completed
|
||||
const markStepCompleted = async () => {
|
||||
@@ -450,7 +367,8 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
if (currentStep === 1) {
|
||||
stepData.bakery_name = bakeryData.name;
|
||||
stepData.bakery_address = bakeryData.address;
|
||||
stepData.business_type = bakeryData.businessType;
|
||||
stepData.business_type = 'bakery'; // Default - will be auto-detected
|
||||
stepData.auto_detect_business_type = true;
|
||||
stepData.products_count = bakeryData.products.length;
|
||||
} else if (currentStep === 2) {
|
||||
stepData.file_name = bakeryData.csvFile?.name;
|
||||
@@ -479,39 +397,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
}
|
||||
return true;
|
||||
case 2:
|
||||
// Skip validation if using smart import (it handles its own validation)
|
||||
if (useSmartImport) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!bakeryData.csvFile) {
|
||||
toast.error('Por favor, selecciona un archivo con tus datos históricos');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate file format
|
||||
const fileName = bakeryData.csvFile.name.toLowerCase();
|
||||
const supportedFormats = ['.csv', '.xlsx', '.xls', '.json'];
|
||||
const isValidFormat = supportedFormats.some(format => fileName.endsWith(format));
|
||||
|
||||
if (!isValidFormat) {
|
||||
toast.error('Formato de archivo no soportado. Usa CSV, Excel (.xlsx, .xls) o JSON');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate file size (10MB limit as per backend)
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
if (bakeryData.csvFile.size > maxSize) {
|
||||
toast.error('El archivo es demasiado grande. Máximo 10MB');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if validation was successful
|
||||
if (validationStatus.status !== 'valid') {
|
||||
toast.error('Por favor, valida el archivo antes de continuar');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Smart import handles its own validation
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
@@ -672,37 +558,20 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||
Tipo de negocio
|
||||
</label>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBakeryData(prev => ({ ...prev, businessType: 'individual' }))}
|
||||
className={`p-4 border rounded-xl text-left transition-all ${
|
||||
bakeryData.businessType === 'individual'
|
||||
? 'border-primary-500 bg-primary-50 text-primary-700'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
<Store className="h-5 w-5 mb-2" />
|
||||
<div className="font-medium">Panadería Individual</div>
|
||||
<div className="text-sm text-gray-500">Un solo punto de venta</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBakeryData(prev => ({ ...prev, businessType: 'central_workshop' }))}
|
||||
className={`p-4 border rounded-xl text-left transition-all ${
|
||||
bakeryData.businessType === 'central_workshop'
|
||||
? 'border-primary-500 bg-primary-50 text-primary-700'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
<Factory className="h-5 w-5 mb-2" />
|
||||
<div className="font-medium">Obrador Central</div>
|
||||
<div className="text-sm text-gray-500">Múltiples puntos de venta</div>
|
||||
</button>
|
||||
<div className="bg-blue-50 border border-blue-200 p-4 rounded-lg">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<Brain className="w-5 h-5 text-blue-500 mt-0.5" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-blue-900">
|
||||
🤖 Detección automática del tipo de negocio
|
||||
</h4>
|
||||
<p className="text-sm text-blue-700 mt-1">
|
||||
Nuestro sistema de IA analizará automáticamente tus datos de ventas para identificar
|
||||
si eres una panadería, cafetería, pastelería o restaurante. No necesitas seleccionar nada manualmente.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -724,360 +593,30 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
);
|
||||
}
|
||||
|
||||
// Use Smart Import by default, with option to switch to traditional
|
||||
if (useSmartImport) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header with import mode toggle */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-gray-900">
|
||||
Importación Inteligente de Datos 🧠
|
||||
</h3>
|
||||
<p className="text-gray-600 mt-1">
|
||||
Nuestra IA creará automáticamente tu inventario desde tus datos históricos
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setUseSmartImport(false)}
|
||||
className="flex items-center space-x-2 px-4 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<Upload className="w-4 h-4" />
|
||||
<span>Importación tradicional</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Smart Import Component */}
|
||||
<SmartHistoricalDataImport
|
||||
tenantId={tenantId}
|
||||
onComplete={(result) => {
|
||||
// Mark sales data as uploaded and proceed to training
|
||||
completeStep('sales_data_uploaded', {
|
||||
smart_import: true,
|
||||
records_imported: result.successful_imports,
|
||||
import_job_id: result.import_job_id,
|
||||
tenant_id: tenantId,
|
||||
user_id: user?.id
|
||||
}).then(() => {
|
||||
setBakeryData(prev => ({ ...prev, hasHistoricalData: true }));
|
||||
startTraining();
|
||||
}).catch(() => {
|
||||
// Continue even if step completion fails
|
||||
setBakeryData(prev => ({ ...prev, hasHistoricalData: true }));
|
||||
startTraining();
|
||||
});
|
||||
}}
|
||||
onBack={() => setUseSmartImport(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Traditional import fallback
|
||||
// Smart Import only
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header with import mode toggle */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
Datos Históricos (Modo Tradicional)
|
||||
</h3>
|
||||
<p className="text-gray-600 mt-1">
|
||||
Sube tus datos y configura tu inventario manualmente
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setUseSmartImport(true)}
|
||||
className="flex items-center space-x-2 px-4 py-2 text-sm bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-lg hover:from-blue-600 hover:to-purple-600 transition-colors"
|
||||
>
|
||||
<Brain className="w-4 h-4" />
|
||||
<span>Activar IA</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 mb-6">
|
||||
Para obtener predicciones precisas, necesitamos tus datos históricos de ventas.
|
||||
Puedes subir archivos en varios formatos.
|
||||
</p>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-200 p-4 rounded-lg mb-6">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
|
||||
<span className="text-white text-sm font-bold">!</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-blue-900">
|
||||
Formatos soportados y estructura de datos
|
||||
</h4>
|
||||
<div className="mt-2 text-sm text-blue-700">
|
||||
<p className="mb-3"><strong>Formatos aceptados:</strong></p>
|
||||
<div className="grid grid-cols-2 gap-4 mb-3">
|
||||
<div>
|
||||
<p className="font-medium">📊 Hojas de cálculo:</p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li>.xlsx (Excel moderno)</li>
|
||||
<li>.xls (Excel clásico)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">📄 Datos estructurados:</p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li>.csv (Valores separados por comas)</li>
|
||||
<li>.json (Formato JSON)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mb-2"><strong>Columnas requeridas (en cualquier idioma):</strong></p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li><strong>Fecha</strong>: fecha, date, datum (formato: YYYY-MM-DD, DD/MM/YYYY, etc.)</li>
|
||||
<li><strong>Producto</strong>: producto, product, item, articulo, nombre</li>
|
||||
<li><strong>Cantidad</strong>: cantidad, quantity, cantidad_vendida, qty</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 p-6 border-2 border-dashed border-gray-300 rounded-xl hover:border-primary-300 transition-colors">
|
||||
<div className="text-center">
|
||||
<Upload className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<div className="mt-4">
|
||||
<label htmlFor="sales-file-upload" className="cursor-pointer">
|
||||
<span className="mt-2 block text-sm font-medium text-gray-900">
|
||||
Subir archivo de datos históricos
|
||||
</span>
|
||||
<span className="mt-1 block text-sm text-gray-500">
|
||||
Arrastra y suelta tu archivo aquí, o haz clic para seleccionar
|
||||
</span>
|
||||
<span className="mt-1 block text-xs text-gray-400">
|
||||
Máximo 10MB - CSV, Excel (.xlsx, .xls), JSON
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="sales-file-upload"
|
||||
type="file"
|
||||
accept=".csv,.xlsx,.xls,.json"
|
||||
required
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
// Validate file size (10MB limit)
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
toast.error('El archivo es demasiado grande. Máximo 10MB.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update bakery data with the selected file
|
||||
setBakeryData(prev => ({
|
||||
...prev,
|
||||
csvFile: file,
|
||||
hasHistoricalData: true
|
||||
}));
|
||||
|
||||
toast.success(`Archivo ${file.name} seleccionado correctamente`);
|
||||
|
||||
// Auto-validate the file after upload if tenantId exists
|
||||
if (tenantId) {
|
||||
setValidationStatus({ status: 'validating' });
|
||||
|
||||
try {
|
||||
const validationResult = await validateSalesData(tenantId, file);
|
||||
|
||||
if (validationResult.is_valid) {
|
||||
setValidationStatus({
|
||||
status: 'valid',
|
||||
message: validationResult.message,
|
||||
records: validationResult.details?.total_records || 0
|
||||
});
|
||||
toast.success('¡Archivo validado correctamente!');
|
||||
} else {
|
||||
setValidationStatus({
|
||||
status: 'invalid',
|
||||
message: validationResult.message
|
||||
});
|
||||
toast.error(`Error en validación: ${validationResult.message}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
setValidationStatus({
|
||||
status: 'invalid',
|
||||
message: 'Error al validar el archivo'
|
||||
});
|
||||
toast.error('Error al validar el archivo');
|
||||
}
|
||||
} else {
|
||||
// If no tenantId yet, set to idle and wait for manual validation
|
||||
setValidationStatus({ status: 'idle' });
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{bakeryData.csvFile ? (
|
||||
<div className="mt-4 space-y-3">
|
||||
<div className="p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="h-5 w-5 text-gray-500 mr-2" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-700">
|
||||
{bakeryData.csvFile.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600">
|
||||
{(bakeryData.csvFile.size / 1024).toFixed(1)} KB • {bakeryData.csvFile.type || 'Archivo de datos'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBakeryData(prev => ({ ...prev, csvFile: undefined }));
|
||||
setValidationStatus({ status: 'idle' });
|
||||
}}
|
||||
className="text-red-600 hover:text-red-800 text-sm"
|
||||
>
|
||||
Quitar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Validation Section */}
|
||||
<div className="p-4 border rounded-lg">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-sm font-medium text-gray-900">
|
||||
Validación de datos
|
||||
</h4>
|
||||
{validationStatus.status === 'validating' ? (
|
||||
<Loader className="h-4 w-4 animate-spin text-blue-500" />
|
||||
) : validationStatus.status === 'valid' ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
) : validationStatus.status === 'invalid' ? (
|
||||
<AlertTriangle className="h-4 w-4 text-red-500" />
|
||||
) : (
|
||||
<Clock className="h-4 w-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{validationStatus.status === 'idle' && tenantId ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-600">
|
||||
Valida tu archivo para verificar que tiene el formato correcto.
|
||||
</p>
|
||||
<button
|
||||
onClick={validateSalesFile}
|
||||
disabled={isLoading}
|
||||
className="w-full px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Validar archivo
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{!tenantId ? (
|
||||
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p className="text-sm text-yellow-800">
|
||||
⚠️ No se ha encontrado la panadería registrada.
|
||||
</p>
|
||||
<p className="text-xs text-yellow-600 mt-1">
|
||||
Ve al paso anterior para registrar tu panadería o espera mientras se carga desde el servidor.
|
||||
</p>
|
||||
</div>
|
||||
) : validationStatus.status !== 'idle' ? (
|
||||
<button
|
||||
onClick={() => setValidationStatus({ status: 'idle' })}
|
||||
className="px-4 py-2 bg-gray-500 text-white text-sm rounded-lg hover:bg-gray-600"
|
||||
>
|
||||
Resetear validación
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'validating' && (
|
||||
<p className="text-sm text-blue-600">
|
||||
Validando archivo... Por favor espera.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'valid' && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-green-700">
|
||||
✅ Archivo validado correctamente
|
||||
</p>
|
||||
{validationStatus.records && (
|
||||
<p className="text-xs text-green-600">
|
||||
{validationStatus.records} registros encontrados
|
||||
</p>
|
||||
)}
|
||||
{validationStatus.message && (
|
||||
<p className="text-xs text-green-600">
|
||||
{validationStatus.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'invalid' && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-red-700">
|
||||
❌ Error en validación
|
||||
</p>
|
||||
{validationStatus.message && (
|
||||
<p className="text-xs text-red-600">
|
||||
{validationStatus.message}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
onClick={validateSalesFile}
|
||||
disabled={isLoading}
|
||||
className="w-full px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Validar de nuevo
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<div className="flex items-center">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-600 mr-2" />
|
||||
<p className="text-sm text-yellow-800">
|
||||
<strong>Archivo requerido:</strong> Selecciona un archivo con tus datos históricos de ventas
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Show switch to smart import suggestion if traditional validation fails */}
|
||||
{validationStatus.status === 'invalid' && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Brain className="w-5 h-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-blue-900 mb-2">
|
||||
💡 ¿Problemas con la validación?
|
||||
</h4>
|
||||
<p className="text-sm text-blue-700 mb-3">
|
||||
Nuestra IA puede manejar archivos con formatos más flexibles y ayudarte a solucionar problemas automáticamente.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setUseSmartImport(true)}
|
||||
className="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Probar importación inteligente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Smart Import Component */}
|
||||
<SmartHistoricalDataImport
|
||||
tenantId={tenantId}
|
||||
onComplete={(result) => {
|
||||
// Mark sales data as uploaded and proceed to training
|
||||
completeStep('sales_data_uploaded', {
|
||||
smart_import: true,
|
||||
records_imported: result.successful_imports,
|
||||
import_job_id: result.import_job_id,
|
||||
tenant_id: tenantId,
|
||||
user_id: user?.id
|
||||
}).then(() => {
|
||||
setBakeryData(prev => ({ ...prev, hasHistoricalData: true }));
|
||||
startTraining();
|
||||
}).catch(() => {
|
||||
// Continue even if step completion fails
|
||||
setBakeryData(prev => ({ ...prev, hasHistoricalData: true }));
|
||||
startTraining();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1369,7 +908,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
</main>
|
||||
|
||||
{/* Navigation with Enhanced Accessibility - Hidden during training (step 3), completion (step 4), and smart import */}
|
||||
{currentStep < 3 && !(currentStep === 2 && useSmartImport) && (
|
||||
{currentStep < 3 && currentStep !== 2 && (
|
||||
<nav
|
||||
className="flex justify-between items-center bg-white rounded-3xl shadow-sm p-6"
|
||||
role="navigation"
|
||||
@@ -1391,16 +930,15 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
<div className="text-sm text-gray-500">Paso {currentStep} de 4</div>
|
||||
<div className="text-xs text-gray-400 mt-1">
|
||||
{currentStep === 1 && "Configura tu panadería"}
|
||||
{currentStep === 2 && "Sube datos de ventas"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dynamic Next Button with contextual text */}
|
||||
<button
|
||||
onClick={handleNext}
|
||||
disabled={isLoading || (currentStep === 2 && validationStatus?.status !== 'valid')}
|
||||
disabled={isLoading}
|
||||
className="flex items-center px-8 py-3 bg-gradient-to-r from-primary-500 to-primary-600 text-white rounded-xl hover:from-primary-600 hover:to-primary-700 transition-all hover:shadow-lg hover:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none group focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
|
||||
aria-label={`${currentStep === 1 ? 'Save bakery information and continue' : currentStep === 2 && validationStatus?.status === 'valid' ? 'Start AI training' : 'Continue to next step'}`}
|
||||
aria-label={`${currentStep === 1 ? 'Save bakery information and continue' : 'Continue to next step'}`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
@@ -1412,11 +950,6 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
) : (
|
||||
<>
|
||||
{currentStep === 1 && "Guardar y Continuar"}
|
||||
{currentStep === 2 && (
|
||||
<>
|
||||
{validationStatus?.status === 'valid' ? "Iniciar Entrenamiento" : "Continuar"}
|
||||
</>
|
||||
)}
|
||||
<ChevronRight className="h-5 w-5 ml-2 group-hover:translate-x-0.5 transition-transform" aria-hidden="true" />
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user