Simplify the onboardinf flow components 4

This commit is contained in:
Urtzi Alfaro
2025-09-08 22:28:26 +02:00
parent a8f6e9d593
commit ddb75f8e55
3 changed files with 101 additions and 109 deletions

View File

@@ -7,6 +7,7 @@ import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useCreateIngredient } from '../../../../api/hooks/inventory';
import { useImportFileOnly } from '../../../../api/hooks/dataImport';
import { useClassifyProductsBatch } from '../../../../api/hooks/classification';
import { useAuth } from '../../../../contexts/AuthContext';
interface UploadSalesDataStepProps {
onNext: () => void;
@@ -64,27 +65,34 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
const fileInputRef = useRef<HTMLInputElement>(null);
const currentTenant = useCurrentTenant();
const { user } = useAuth();
const { validateFile } = useValidateFileOnly();
const createIngredient = useCreateIngredient();
const { importFile } = useImportFileOnly();
const classifyProducts = useClassifyProductsBatch();
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
setSelectedFile(file);
setValidationResult(null);
setError('');
// Automatically trigger validation and classification
await handleAutoValidateAndClassify(file);
}
};
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
const file = event.dataTransfer.files[0];
if (file) {
setSelectedFile(file);
setValidationResult(null);
setError('');
// Automatically trigger validation and classification
await handleAutoValidateAndClassify(file);
}
};
@@ -92,67 +100,69 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
event.preventDefault();
};
const handleValidateFile = async () => {
if (!selectedFile || !currentTenant?.id) return;
const handleAutoValidateAndClassify = async (file: File) => {
if (!currentTenant?.id) return;
setIsValidating(true);
setError('');
setProgressState({ stage: 'preparing', progress: 0, message: 'Preparando validación del archivo...' });
setProgressState({ stage: 'preparing', progress: 0, message: 'Preparando validación automática del archivo...' });
try {
// Step 1: Validate the file
const result = await validateFile(
currentTenant.id,
selectedFile,
file,
{
onProgress: (stage: string, progress: number, message: string) => {
setProgressState({ stage, progress, message });
// Map validation progress to 0-50%
setProgressState({ stage, progress: Math.min(progress * 0.5, 50), message });
}
}
);
if (result.success && result.validationResult) {
setValidationResult(result.validationResult);
setProgressState(null);
setProgressState({ stage: 'analyzing', progress: 60, message: 'Validación exitosa. Generando sugerencias automáticamente...' });
// Step 2: Automatically trigger classification
await generateInventorySuggestionsAuto(result.validationResult);
} else {
setError(result.error || 'Error al validar el archivo');
setProgressState(null);
setIsValidating(false);
}
} catch (error) {
setError('Error validando archivo: ' + (error instanceof Error ? error.message : 'Error desconocido'));
setProgressState(null);
}
setIsValidating(false);
};
const handleContinue = () => {
if (validationResult) {
// Generate inventory suggestions based on validation
generateInventorySuggestions();
setIsValidating(false);
}
};
const generateInventorySuggestions = async () => {
if (!currentTenant?.id || !validationResult) {
const generateInventorySuggestionsAuto = async (validationData: ImportValidationResponse) => {
if (!currentTenant?.id) {
setError('No hay datos de validación disponibles para generar sugerencias');
setIsValidating(false);
setProgressState(null);
return;
}
setProgressState({ stage: 'analyzing', progress: 25, message: 'Analizando productos de ventas...' });
try {
setProgressState({ stage: 'analyzing', progress: 65, message: 'Analizando productos de ventas...' });
// Extract product data from validation result - use the exact backend structure
const products = validationResult.product_list?.map((productName: string) => ({
const products = validationData.product_list?.map((productName: string) => ({
product_name: productName
})) || [];
if (products.length === 0) {
setError('No se encontraron productos en los datos de ventas');
setProgressState(null);
setIsValidating(false);
return;
}
setProgressState({ stage: 'classifying', progress: 50, message: 'Clasificando productos con IA...' });
setProgressState({ stage: 'classifying', progress: 75, message: 'Clasificando productos con IA...' });
// Call the classification API
const suggestions = await classifyProducts.mutateAsync({
@@ -160,7 +170,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
batchData: { products }
});
setProgressState({ stage: 'preparing', progress: 75, message: 'Preparando sugerencias de inventario...' });
setProgressState({ stage: 'preparing', progress: 90, message: 'Preparando sugerencias de inventario...' });
// Convert API response to InventoryItem format - use exact backend structure plus UI fields
const items: InventoryItem[] = suggestions.map(suggestion => {
@@ -201,13 +211,16 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
setInventoryItems(items);
setShowInventoryStep(true);
setProgressState(null);
setIsValidating(false);
} catch (err) {
console.error('Error generating inventory suggestions:', err);
setError('Error al generar sugerencias de inventario. Por favor, inténtalo de nuevo.');
setProgressState(null);
setIsValidating(false);
}
};
const handleToggleSelection = (id: string) => {
setInventoryItems(items =>
items.map(item =>
@@ -328,7 +341,10 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
totalItems: selectedItems.length,
validationResult,
file: selectedFile,
salesImportResult
salesImportResult,
inventoryConfigured: true, // Flag for ML training dependency
shouldAutoCompleteSuppliers: true, // Flag to trigger suppliers auto-completion after step completion
userId: user?.id // Pass user ID for suppliers completion
});
} catch (err) {
console.error('Error creating inventory items:', err);
@@ -354,7 +370,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
<div className="space-y-6">
<div className="text-center">
<p className="text-[var(--text-secondary)] mb-6">
Basado en tus datos de ventas, hemos generado estas sugerencias de inventario.
¡Perfecto! Hemos analizado automáticamente tus datos de ventas y generado estas sugerencias de inventario inteligentes.
Revisa y selecciona los artículos que te gustaría agregar a tu inventario.
</p>
</div>
@@ -487,14 +503,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
)}
{/* Actions */}
<div className="flex flex-col sm:flex-row justify-between gap-3 sm:gap-0">
<Button
variant="outline"
onClick={() => setShowInventoryStep(false)}
className="order-2 sm:order-1 w-full sm:w-auto"
>
Volver
</Button>
<div className="flex flex-col sm:flex-row justify-end gap-3 sm:gap-0">{/* Removed back button */}
<Button
onClick={handleCreateInventory}
@@ -502,7 +511,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
loadingText="Creando Inventario..."
size="lg"
disabled={selectedCount === 0}
className="order-1 sm:order-2 w-full sm:w-auto"
className="w-full sm:w-auto"
>
<span className="hidden sm:inline">
Crear {selectedCount} Artículo{selectedCount !== 1 ? 's' : ''} de Inventario
@@ -520,7 +529,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
<div className="space-y-6">
<div className="text-center">
<p className="text-[var(--text-secondary)] mb-6">
Sube tus datos de ventas (formato CSV o JSON) para generar sugerencias de inventario inteligentes.
Sube tus datos de ventas (formato CSV o JSON) y automáticamente validaremos y generaremos sugerencias de inventario inteligentes.
</p>
</div>
@@ -570,7 +579,8 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
<p className="text-lg font-medium">Drop your sales data here</p>
<p className="text-[var(--text-secondary)]">or click to browse files</p>
<p className="text-sm text-[var(--text-tertiary)] mt-2">
Supported formats: CSV, JSON (max 100MB)
Supported formats: CSV, JSON (max 100MB)<br/>
<span className="text-[var(--color-primary)]">Auto-validates and generates suggestions</span>
</p>
</div>
<Button
@@ -635,39 +645,25 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
)}
{/* Actions */}
<div className="flex flex-col sm:flex-row justify-between gap-3 sm:gap-0">
<Button
variant="outline"
onClick={onPrevious}
disabled={isFirstStep}
className="order-2 sm:order-1 w-full sm:w-auto"
>
Anterior
</Button>
<div className="flex flex-col sm:flex-row justify-end gap-3 sm:gap-0">{/* Removed back button */}
<div className="flex flex-col sm:flex-row gap-3 order-1 sm:order-2">
{selectedFile && !validationResult && (
<Button
onClick={handleValidateFile}
isLoading={isValidating}
loadingText="Validando..."
size="lg"
className="w-full sm:w-auto"
>
Validar Archivo
</Button>
)}
{validationResult && (
<Button
onClick={handleContinue}
size="lg"
className="w-full sm:w-auto"
>
Continuar con estos Datos
</Button>
)}
</div>
{selectedFile && !showInventoryStep && (
<div className="flex items-center justify-center px-4 py-2 bg-[var(--bg-secondary)] rounded-lg">
{isValidating ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-[var(--color-primary)] mr-2"></div>
<span className="text-sm text-[var(--text-secondary)]">Procesando automáticamente...</span>
</>
) : validationResult ? (
<>
<svg className="w-4 h-4 text-[var(--color-success)] mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-sm text-[var(--color-success)]">Archivo procesado exitosamente</span>
</>
) : null}
</div>
)}
</div>
</div>
);