Start integrating the onboarding flow with backend 10
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Store, MapPin, Phone, Mail, Hash, Building } from 'lucide-react';
|
||||
import { Button, Card, Input } from '../../../ui';
|
||||
import { OnboardingStepProps } from '../OnboardingWizard';
|
||||
import { useAuthUser } from '../../../../stores/auth.store';
|
||||
import { useOnboarding } from '../../../../hooks/business/onboarding';
|
||||
|
||||
// Backend-compatible bakery setup interface
|
||||
interface BakerySetupData {
|
||||
@@ -24,6 +26,18 @@ export const BakerySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
isFirstStep,
|
||||
isLastStep
|
||||
}) => {
|
||||
const user = useAuthUser();
|
||||
const userId = user?.id;
|
||||
|
||||
// Business onboarding hooks
|
||||
const {
|
||||
updateStepData,
|
||||
tenantCreation,
|
||||
isLoading,
|
||||
error,
|
||||
clearError
|
||||
} = useOnboarding();
|
||||
|
||||
const [formData, setFormData] = useState<BakerySetupData>({
|
||||
name: data.bakery?.name || '',
|
||||
business_type: data.bakery?.business_type || 'bakery',
|
||||
@@ -63,15 +77,33 @@ export const BakerySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
}
|
||||
];
|
||||
|
||||
const lastFormDataRef = useRef(formData);
|
||||
|
||||
useEffect(() => {
|
||||
// Update parent data when form changes
|
||||
onDataChange({
|
||||
bakery: {
|
||||
// Only update if formData actually changed and is valid
|
||||
if (JSON.stringify(formData) !== JSON.stringify(lastFormDataRef.current)) {
|
||||
lastFormDataRef.current = formData;
|
||||
|
||||
// Update parent data when form changes
|
||||
const bakeryData = {
|
||||
...formData,
|
||||
tenant_id: data.bakery?.tenant_id
|
||||
};
|
||||
|
||||
onDataChange({ bakery: bakeryData });
|
||||
}
|
||||
}, [formData, onDataChange, data.bakery?.tenant_id]);
|
||||
|
||||
// Separate effect for hook updates to avoid circular dependencies
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (userId && Object.values(formData).some(value => value.trim() !== '')) {
|
||||
updateStepData('setup', { bakery: formData });
|
||||
}
|
||||
});
|
||||
}, [formData]);
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [formData, userId, updateStepData]);
|
||||
|
||||
const handleInputChange = (field: keyof BakerySetupData, value: string) => {
|
||||
setFormData(prev => ({
|
||||
@@ -81,6 +113,21 @@ export const BakerySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
};
|
||||
|
||||
|
||||
// Validate form data for completion
|
||||
const isFormValid = () => {
|
||||
return !!(
|
||||
formData.name &&
|
||||
formData.address &&
|
||||
formData.city &&
|
||||
formData.postal_code &&
|
||||
formData.phone &&
|
||||
formData.business_type &&
|
||||
formData.business_model
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
|
||||
@@ -227,6 +274,16 @@ export const BakerySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Show loading state for tenant creation */}
|
||||
{tenantCreation.isLoading && (
|
||||
<div className="text-center p-4 bg-blue-50 rounded-lg">
|
||||
<div className="animate-spin w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full mx-auto mb-2" />
|
||||
<p className="text-blue-600 text-sm">
|
||||
Configurando panadería...
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show loading state when creating tenant */}
|
||||
{data.bakery?.isCreating && (
|
||||
<div className="text-center p-6 bg-[var(--color-primary)]/5 rounded-lg">
|
||||
@@ -237,6 +294,24 @@ export const BakerySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show error state for business onboarding operations */}
|
||||
{error && (
|
||||
<div className="text-center p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-600 font-medium mb-2">
|
||||
Error al configurar panadería
|
||||
</p>
|
||||
<p className="text-red-500 text-sm">
|
||||
{error}
|
||||
</p>
|
||||
<button
|
||||
onClick={clearError}
|
||||
className="mt-2 text-sm text-red-600 underline hover:no-underline"
|
||||
>
|
||||
Ocultar error
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show error state if tenant creation fails */}
|
||||
{data.bakery?.creationError && (
|
||||
<div className="text-center p-6 bg-red-50 border border-red-200 rounded-lg">
|
||||
@@ -248,6 +323,7 @@ export const BakerySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -57,6 +57,7 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
|
||||
validationResults,
|
||||
suggestions
|
||||
},
|
||||
tenantCreation,
|
||||
isLoading,
|
||||
error,
|
||||
clearError
|
||||
@@ -70,22 +71,44 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
|
||||
|
||||
// Get tenant ID from multiple sources with fallback
|
||||
const getTenantId = (): string | null => {
|
||||
const tenantId = currentTenant?.id || user?.tenant_id || null;
|
||||
// Also check the onboarding data for tenant creation success
|
||||
const onboardingTenantId = data.bakery?.tenant_id;
|
||||
const tenantId = currentTenant?.id || user?.tenant_id || onboardingTenantId || null;
|
||||
console.log('DataProcessingStep - getTenantId:', {
|
||||
currentTenant: currentTenant?.id,
|
||||
userTenantId: user?.tenant_id,
|
||||
onboardingTenantId: onboardingTenantId,
|
||||
finalTenantId: tenantId,
|
||||
isLoadingUserData,
|
||||
authLoading,
|
||||
tenantLoading,
|
||||
user: user ? { id: user.id, email: user.email } : null
|
||||
user: user ? { id: user.id, email: user.email } : null,
|
||||
tenantCreationSuccess: data.tenantCreation?.isSuccess
|
||||
});
|
||||
return tenantId;
|
||||
};
|
||||
|
||||
// Check if tenant data is available (not loading and has ID)
|
||||
// Check if tenant data is available (not loading and has ID, OR tenant was created successfully)
|
||||
const isTenantAvailable = (): boolean => {
|
||||
return !isLoadingUserData && getTenantId() !== null;
|
||||
const hasAuth = !authLoading && user;
|
||||
const hasTenantId = getTenantId() !== null;
|
||||
const tenantCreatedSuccessfully = tenantCreation.isSuccess;
|
||||
const tenantCreatedInOnboarding = data.bakery?.tenantCreated === true;
|
||||
|
||||
const isAvailable = hasAuth && (hasTenantId || tenantCreatedSuccessfully || tenantCreatedInOnboarding);
|
||||
console.log('DataProcessingStep - isTenantAvailable:', {
|
||||
hasAuth,
|
||||
hasTenantId,
|
||||
tenantCreatedSuccessfully,
|
||||
tenantCreatedInOnboarding,
|
||||
isAvailable,
|
||||
authLoading,
|
||||
tenantLoading,
|
||||
tenantCreationFromHook: tenantCreation,
|
||||
bakeryData: data.bakery
|
||||
});
|
||||
|
||||
return isAvailable;
|
||||
};
|
||||
// Use onboarding hook state when available, fallback to local state
|
||||
const [localStage, setLocalStage] = useState<ProcessingStage>(data.processingStage || 'upload');
|
||||
@@ -93,38 +116,67 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
|
||||
const [localResults, setLocalResults] = useState<ProcessingResult | null>(data.processingResults || null);
|
||||
|
||||
// Derive current state from onboarding hooks or local state
|
||||
const stage = onboardingStage || localStage;
|
||||
// Priority: if local is 'completed' or 'error', use local; otherwise use onboarding state
|
||||
const stage = (localStage === 'completed' || localStage === 'error')
|
||||
? localStage
|
||||
: (onboardingStage || localStage);
|
||||
const progress = onboardingProgress || 0;
|
||||
const currentMessage = onboardingMessage || '';
|
||||
const results = (validationResults && suggestions) ? {
|
||||
...validationResults,
|
||||
aiSuggestions: suggestions,
|
||||
// Add calculated fields
|
||||
productsIdentified: validationResults.product_list?.length || 0,
|
||||
// Add calculated fields from backend response
|
||||
productsIdentified: validationResults.unique_products || validationResults.product_list?.length || 0,
|
||||
categoriesDetected: suggestions ? new Set(suggestions.map((s: any) => s.category)).size : 0,
|
||||
businessModel: 'production',
|
||||
confidenceScore: 85,
|
||||
recommendations: []
|
||||
recommendations: validationResults.summary?.suggestions || [],
|
||||
// Backend response details
|
||||
totalRecords: validationResults.total_records || 0,
|
||||
validRecords: validationResults.valid_records || 0,
|
||||
invalidRecords: validationResults.invalid_records || 0,
|
||||
fileFormat: validationResults.summary?.file_format || 'csv',
|
||||
fileSizeMb: validationResults.summary?.file_size_mb || 0,
|
||||
estimatedProcessingTime: validationResults.summary?.estimated_processing_time_seconds || 0,
|
||||
detectedColumns: validationResults.summary?.detected_columns || [],
|
||||
validationMessage: validationResults.message || 'Validación completada'
|
||||
} : localResults;
|
||||
|
||||
// Debug logging for state changes
|
||||
console.log('DataProcessingStep - State debug:', {
|
||||
localStage,
|
||||
onboardingStage,
|
||||
finalStage: stage,
|
||||
hasValidationResults: !!validationResults,
|
||||
hasSuggestions: !!suggestions,
|
||||
hasResults: !!results,
|
||||
localResults: !!localResults
|
||||
});
|
||||
const [dragActive, setDragActive] = useState(false);
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const lastStateRef = useRef({ stage, progress, currentMessage, results, suggestions, uploadedFile });
|
||||
|
||||
useEffect(() => {
|
||||
// Update parent data when state changes
|
||||
onDataChange({
|
||||
...data,
|
||||
processingStage: stage,
|
||||
processingProgress: progress,
|
||||
currentMessage: currentMessage,
|
||||
processingResults: results,
|
||||
suggestions: suggestions,
|
||||
files: {
|
||||
...data.files,
|
||||
salesData: uploadedFile
|
||||
}
|
||||
});
|
||||
}, [stage, progress, currentMessage, results, suggestions, uploadedFile, onDataChange, data]);
|
||||
// Only update if state actually changed
|
||||
const currentState = { stage, progress, currentMessage, results, suggestions, uploadedFile };
|
||||
if (JSON.stringify(currentState) !== JSON.stringify(lastStateRef.current)) {
|
||||
lastStateRef.current = currentState;
|
||||
|
||||
// Update parent data when state changes
|
||||
onDataChange({
|
||||
processingStage: stage,
|
||||
processingProgress: progress,
|
||||
currentMessage: currentMessage,
|
||||
processingResults: results,
|
||||
suggestions: suggestions,
|
||||
files: {
|
||||
...data.files,
|
||||
salesData: uploadedFile
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [stage, progress, currentMessage, results, suggestions, uploadedFile, onDataChange, data.files]);
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -190,7 +242,12 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('DataProcessingStep - Starting file processing');
|
||||
console.log('DataProcessingStep - Starting file processing', {
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
fileType: file.type,
|
||||
lastModified: file.lastModified
|
||||
});
|
||||
|
||||
// Use the onboarding hook for file processing
|
||||
const success = await processSalesFile(file, (progress, stage, message) => {
|
||||
@@ -199,6 +256,21 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
|
||||
|
||||
if (success) {
|
||||
setLocalStage('completed');
|
||||
|
||||
// If we have results from the onboarding hook, store them locally too
|
||||
if (validationResults && suggestions) {
|
||||
const processedResults = {
|
||||
...validationResults,
|
||||
aiSuggestions: suggestions,
|
||||
productsIdentified: validationResults.product_list?.length || 0,
|
||||
categoriesDetected: suggestions ? new Set(suggestions.map((s: any) => s.category)).size : 0,
|
||||
businessModel: 'production',
|
||||
confidenceScore: 85,
|
||||
recommendations: []
|
||||
};
|
||||
setLocalResults(processedResults);
|
||||
}
|
||||
|
||||
toast.addToast('El archivo se procesó correctamente', {
|
||||
title: 'Procesamiento completado',
|
||||
type: 'success'
|
||||
@@ -209,6 +281,15 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
|
||||
|
||||
} catch (error) {
|
||||
console.error('DataProcessingStep - Processing error:', error);
|
||||
console.error('DataProcessingStep - Error details:', {
|
||||
error,
|
||||
errorMessage: error instanceof Error ? error.message : 'Unknown error',
|
||||
errorStack: error instanceof Error ? error.stack : undefined,
|
||||
uploadedFile: file?.name,
|
||||
fileSize: file?.size,
|
||||
fileType: file?.type,
|
||||
localUploadedFile: uploadedFile?.name
|
||||
});
|
||||
|
||||
setLocalStage('error');
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error en el procesamiento de datos';
|
||||
@@ -471,15 +552,29 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
|
||||
¡Procesamiento Completado!
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] max-w-2xl mx-auto">
|
||||
Tus datos han sido procesados exitosamente
|
||||
{results.validationMessage || 'Tus datos han sido procesados exitosamente'}
|
||||
</p>
|
||||
{results.fileSizeMb && (
|
||||
<div className="text-xs text-[var(--text-tertiary)] mt-2">
|
||||
Archivo {results.fileFormat?.toUpperCase()} • {results.fileSizeMb.toFixed(2)} MB
|
||||
{results.estimatedProcessingTime && ` • ${results.estimatedProcessingTime}s procesamiento`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Simple Stats Cards */}
|
||||
{/* Enhanced Stats Cards */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center p-4 bg-[var(--color-info)]/10 rounded-lg">
|
||||
<p className="text-2xl font-bold text-[var(--color-info)]">{results.total_records}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Registros</p>
|
||||
<p className="text-2xl font-bold text-[var(--color-info)]">{results.totalRecords || results.total_records}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Total Registros</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-[var(--color-success)]/10 rounded-lg">
|
||||
<p className="text-2xl font-bold text-[var(--color-success)]">{results.validRecords || results.valid_records}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Válidos</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-[var(--color-error)]/10 rounded-lg">
|
||||
<p className="text-2xl font-bold text-[var(--color-error)]">{results.invalidRecords || results.invalid_records || 0}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Inválidos</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-[var(--color-primary)]/10 rounded-lg">
|
||||
@@ -500,6 +595,41 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
|
||||
<p className="text-sm text-[var(--text-secondary)]">Modelo</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Additional Details from Backend */}
|
||||
{(results.detectedColumns?.length > 0 || results.recommendations?.length > 0) && (
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* Detected Columns */}
|
||||
{results.detectedColumns?.length > 0 && (
|
||||
<Card className="p-4">
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3">Columnas Detectadas</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{results.detectedColumns.map((column, index) => (
|
||||
<span key={index}
|
||||
className="px-2 py-1 bg-[var(--color-primary)]/10 text-[var(--color-primary)] text-xs rounded-full border border-[var(--color-primary)]/20">
|
||||
{column}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Backend Recommendations */}
|
||||
{results.recommendations?.length > 0 && (
|
||||
<Card className="p-4">
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3">Recomendaciones</h4>
|
||||
<ul className="space-y-2">
|
||||
{results.recommendations.slice(0, 3).map((rec, index) => (
|
||||
<li key={index} className="text-sm text-[var(--text-secondary)] flex items-start">
|
||||
<span className="text-[var(--color-success)] mr-2">•</span>
|
||||
{rec}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Package, Calendar, AlertTriangle, Plus, Edit, Trash2, CheckCircle } from 'lucide-react';
|
||||
import { Button, Card, Input, Badge } from '../../../ui';
|
||||
import { OnboardingStepProps } from '../OnboardingWizard';
|
||||
@@ -31,8 +31,8 @@ interface InventoryItem {
|
||||
const convertProductsToInventory = (approvedProducts: any[]): InventoryItem[] => {
|
||||
return approvedProducts.map((product, index) => ({
|
||||
id: `inventory-${index}`,
|
||||
name: product.suggested_name || product.name,
|
||||
category: product.product_type || 'finished_product',
|
||||
name: product.suggested_name || product.original_name,
|
||||
category: product.product_type === 'ingredient' ? 'ingredient' : 'finished_product',
|
||||
current_stock: 0, // To be configured by user
|
||||
min_stock: 1, // Default minimum
|
||||
max_stock: 100, // Default maximum
|
||||
@@ -62,8 +62,9 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
const currentTenant = useCurrentTenant();
|
||||
const { showToast } = useToast();
|
||||
|
||||
// Use the onboarding hooks
|
||||
// Use the business onboarding hooks
|
||||
const {
|
||||
updateStepData,
|
||||
createInventoryFromSuggestions,
|
||||
importSalesData,
|
||||
inventorySetup: {
|
||||
@@ -72,6 +73,7 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
salesImportResult,
|
||||
isInventoryConfigured
|
||||
},
|
||||
allStepData,
|
||||
isLoading,
|
||||
error,
|
||||
clearError
|
||||
@@ -79,11 +81,16 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
|
||||
const createAlert = (alert: any) => {
|
||||
console.log('Alert:', alert);
|
||||
showToast({
|
||||
title: alert.title,
|
||||
message: alert.message,
|
||||
type: alert.type
|
||||
});
|
||||
if (showToast && typeof showToast === 'function') {
|
||||
showToast({
|
||||
title: alert.title,
|
||||
message: alert.message,
|
||||
type: alert.type
|
||||
});
|
||||
} else {
|
||||
// Fallback to console if showToast is not available
|
||||
console.warn(`Toast would show: ${alert.title} - ${alert.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Use modal for confirmations and editing
|
||||
@@ -93,6 +100,7 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [editingItem, setEditingItem] = useState<InventoryItem | null>(null);
|
||||
const [isAddingNew, setIsAddingNew] = useState(false);
|
||||
const [inventoryCreationAttempted, setInventoryCreationAttempted] = useState(false);
|
||||
|
||||
// Generate inventory items from approved products
|
||||
const generateInventoryFromProducts = (approvedProducts: any[]): InventoryItem[] => {
|
||||
@@ -115,20 +123,24 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
if (data.inventoryItems) {
|
||||
return data.inventoryItems;
|
||||
}
|
||||
// Try to get approved products from current step data first, then from review step data
|
||||
const approvedProducts = data.approvedProducts || data.allStepData?.['review']?.approvedProducts;
|
||||
// Try to get approved products from business hooks data first, then from component props
|
||||
const approvedProducts = data.approvedProducts ||
|
||||
allStepData?.['review']?.approvedProducts ||
|
||||
data.allStepData?.['review']?.approvedProducts;
|
||||
return generateInventoryFromProducts(approvedProducts || []);
|
||||
});
|
||||
|
||||
// Update items when approved products become available (for when component is already mounted)
|
||||
useEffect(() => {
|
||||
const approvedProducts = data.approvedProducts || data.allStepData?.['review']?.approvedProducts;
|
||||
const approvedProducts = data.approvedProducts ||
|
||||
allStepData?.['review']?.approvedProducts ||
|
||||
data.allStepData?.['review']?.approvedProducts;
|
||||
|
||||
if (approvedProducts && approvedProducts.length > 0 && items.length === 0) {
|
||||
const newItems = generateInventoryFromProducts(approvedProducts);
|
||||
setItems(newItems);
|
||||
}
|
||||
}, [data.approvedProducts, data.allStepData]);
|
||||
}, [data.approvedProducts, allStepData, data.allStepData]);
|
||||
|
||||
const [filterCategory, setFilterCategory] = useState<'all' | 'ingredient' | 'finished_product'>('all');
|
||||
|
||||
@@ -136,20 +148,25 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
? items
|
||||
: items.filter(item => item.category === filterCategory);
|
||||
|
||||
// Create inventory items via API using hooks
|
||||
// Create inventory items via API using business hooks
|
||||
const handleCreateInventory = async () => {
|
||||
console.log('InventorySetup - Starting handleCreateInventory');
|
||||
console.log('InventorySetup - data:', data);
|
||||
console.log('InventorySetup - data.allStepData keys:', Object.keys(data.allStepData || {}));
|
||||
|
||||
const approvedProducts = data.approvedProducts || data.allStepData?.['review']?.approvedProducts;
|
||||
console.log('InventorySetup - approvedProducts:', approvedProducts);
|
||||
const approvedProducts = data.approvedProducts ||
|
||||
allStepData?.['review']?.approvedProducts ||
|
||||
data.allStepData?.['review']?.approvedProducts;
|
||||
console.log('InventorySetup - approvedProducts:', {
|
||||
fromDataProp: data.approvedProducts,
|
||||
fromAllStepData: allStepData?.['review']?.approvedProducts,
|
||||
fromDataAllStepData: data.allStepData?.['review']?.approvedProducts,
|
||||
finalProducts: approvedProducts,
|
||||
allStepDataKeys: Object.keys(allStepData || {}),
|
||||
dataKeys: Object.keys(data || {})
|
||||
});
|
||||
|
||||
// Get tenant ID from current tenant context or user
|
||||
const tenantId = currentTenant?.id || user?.tenant_id;
|
||||
console.log('InventorySetup - tenantId from currentTenant:', currentTenant?.id);
|
||||
console.log('InventorySetup - tenantId from user:', user?.tenant_id);
|
||||
console.log('InventorySetup - final tenantId:', tenantId);
|
||||
console.log('InventorySetup - tenantId:', tenantId);
|
||||
|
||||
if (!tenantId || !approvedProducts || approvedProducts.length === 0) {
|
||||
console.log('InventorySetup - Missing requirements: tenantId =', tenantId, 'approvedProducts length =', approvedProducts?.length);
|
||||
@@ -167,162 +184,112 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
|
||||
setIsCreating(true);
|
||||
try {
|
||||
// Create ingredients one by one using the inventory hook
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
const createdItems: any[] = [];
|
||||
const inventoryMapping: { [productName: string]: string } = {};
|
||||
|
||||
for (const [index, product] of approvedProducts.entries()) {
|
||||
const ingredientData = {
|
||||
name: product.suggested_name || product.name,
|
||||
category: product.category || 'general',
|
||||
unit_of_measure: product.unit_of_measure || 'unit',
|
||||
shelf_life_days: product.estimated_shelf_life_days || 30,
|
||||
requires_refrigeration: product.requires_refrigeration || false,
|
||||
requires_freezing: product.requires_freezing || false,
|
||||
is_seasonal: product.is_seasonal || false,
|
||||
minimum_stock_level: 0,
|
||||
maximum_stock_level: 1000,
|
||||
reorder_point: 10
|
||||
};
|
||||
|
||||
try {
|
||||
// Use the onboarding hook's inventory creation method
|
||||
const response = await createInventoryFromSuggestions([{
|
||||
suggestion_id: product.suggestion_id || `suggestion-${Date.now()}-${index}`,
|
||||
original_name: product.original_name || product.name,
|
||||
suggested_name: product.suggested_name || product.name,
|
||||
product_type: product.product_type || 'finished_product',
|
||||
category: product.category || 'general',
|
||||
unit_of_measure: product.unit_of_measure || 'unit',
|
||||
confidence_score: product.confidence_score || 0.8,
|
||||
estimated_shelf_life_days: product.estimated_shelf_life_days || 30,
|
||||
requires_refrigeration: product.requires_refrigeration || false,
|
||||
requires_freezing: product.requires_freezing || false,
|
||||
is_seasonal: product.is_seasonal || false,
|
||||
suggested_supplier: product.suggested_supplier,
|
||||
notes: product.notes
|
||||
}]);
|
||||
const success = !!response;
|
||||
if (success) {
|
||||
successCount++;
|
||||
// Mock created item data since hook doesn't return it
|
||||
const createdItem = { ...ingredientData, id: `created-${Date.now()}-${successCount}` };
|
||||
createdItems.push(createdItem);
|
||||
inventoryMapping[product.original_name || product.name] = createdItem.id;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (ingredientError) {
|
||||
console.error('Error creating ingredient:', product.name, ingredientError);
|
||||
failCount++;
|
||||
// For onboarding, continue even if backend is not ready
|
||||
// Mock success for onboarding flow
|
||||
successCount++;
|
||||
const createdItem = { ...ingredientData, id: `created-${Date.now()}-${successCount}` };
|
||||
createdItems.push(createdItem);
|
||||
inventoryMapping[product.original_name || product.name] = createdItem.id;
|
||||
}
|
||||
}
|
||||
// Approved products should already be in ProductSuggestionResponse format
|
||||
// Just ensure they have all required fields
|
||||
const suggestions = approvedProducts.map((product: any, index: number) => ({
|
||||
suggestion_id: product.suggestion_id || `suggestion-${Date.now()}-${index}`,
|
||||
original_name: product.original_name,
|
||||
suggested_name: product.suggested_name,
|
||||
product_type: product.product_type,
|
||||
category: product.category,
|
||||
unit_of_measure: product.unit_of_measure,
|
||||
confidence_score: product.confidence_score || 0.8,
|
||||
estimated_shelf_life_days: product.estimated_shelf_life_days,
|
||||
requires_refrigeration: product.requires_refrigeration,
|
||||
requires_freezing: product.requires_freezing,
|
||||
is_seasonal: product.is_seasonal,
|
||||
suggested_supplier: product.suggested_supplier,
|
||||
notes: product.notes
|
||||
}));
|
||||
|
||||
// Show results
|
||||
if (successCount > 0) {
|
||||
// Use business onboarding hook to create inventory
|
||||
const inventorySuccess = await createInventoryFromSuggestions(suggestions);
|
||||
|
||||
if (inventorySuccess) {
|
||||
createAlert({
|
||||
type: 'success',
|
||||
category: 'system',
|
||||
priority: 'medium',
|
||||
title: 'Inventario creado',
|
||||
message: `Se crearon ${successCount} elementos de inventario exitosamente.`,
|
||||
message: `Se crearon ${suggestions.length} elementos de inventario exitosamente.`,
|
||||
source: 'onboarding'
|
||||
});
|
||||
} else if (failCount > 0) {
|
||||
|
||||
// Now try to import sales data if available
|
||||
const salesDataFile = data.allStepData?.['data-processing']?.salesDataFile ||
|
||||
allStepData?.['data-processing']?.salesDataFile;
|
||||
const processingResults = data.allStepData?.['data-processing']?.processingResults ||
|
||||
allStepData?.['data-processing']?.processingResults;
|
||||
|
||||
if (salesDataFile && processingResults?.is_valid && inventoryMapping) {
|
||||
try {
|
||||
createAlert({
|
||||
type: 'info',
|
||||
category: 'system',
|
||||
priority: 'medium',
|
||||
title: 'Subiendo datos de ventas',
|
||||
message: 'Subiendo historial de ventas al sistema para entrenamiento de IA...',
|
||||
source: 'onboarding'
|
||||
});
|
||||
|
||||
const salesSuccess = await importSalesData(processingResults, inventoryMapping);
|
||||
|
||||
if (salesSuccess) {
|
||||
createAlert({
|
||||
type: 'success',
|
||||
category: 'system',
|
||||
priority: 'medium',
|
||||
title: 'Datos de ventas subidos',
|
||||
message: `Se subieron ${processingResults.total_records} registros de ventas al sistema exitosamente.`,
|
||||
source: 'onboarding'
|
||||
});
|
||||
}
|
||||
} catch (salesError) {
|
||||
console.error('Error uploading sales data:', salesError);
|
||||
createAlert({
|
||||
type: 'error',
|
||||
category: 'system',
|
||||
priority: 'high',
|
||||
title: 'Error al subir datos de ventas',
|
||||
message: 'El inventario se creó correctamente, pero hubo un problema al subir los datos de ventas.',
|
||||
source: 'onboarding'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update component data and business hook data
|
||||
const updatedData = {
|
||||
...data,
|
||||
inventoryItems: items,
|
||||
inventoryConfigured: true,
|
||||
inventoryCreated: true,
|
||||
inventoryMapping,
|
||||
createdInventoryItems: createdItems,
|
||||
salesImportResult
|
||||
};
|
||||
onDataChange(updatedData);
|
||||
|
||||
// Also update step data in business hooks
|
||||
updateStepData('inventory', {
|
||||
inventoryItems: items,
|
||||
inventoryConfigured: true,
|
||||
inventoryCreated: true,
|
||||
inventoryMapping,
|
||||
createdInventoryItems: createdItems,
|
||||
salesImportResult
|
||||
});
|
||||
|
||||
} else {
|
||||
createAlert({
|
||||
type: 'error',
|
||||
category: 'system',
|
||||
priority: 'high',
|
||||
title: 'Error al crear inventario',
|
||||
message: `No se pudieron crear los elementos de inventario. Backend no disponible.`,
|
||||
message: 'No se pudieron crear los elementos de inventario.',
|
||||
source: 'onboarding'
|
||||
});
|
||||
// Don't continue with sales import if inventory creation failed
|
||||
return;
|
||||
}
|
||||
|
||||
// Now upload sales data to backend (required for ML training)
|
||||
const salesDataFile = data.allStepData?.['data-processing']?.salesDataFile;
|
||||
const processingResults = data.allStepData?.['data-processing']?.processingResults;
|
||||
console.log('InventorySetup - salesDataFile:', salesDataFile);
|
||||
console.log('InventorySetup - processingResults:', processingResults);
|
||||
let salesImportResult = null;
|
||||
|
||||
if (salesDataFile && processingResults?.is_valid) {
|
||||
try {
|
||||
createAlert({
|
||||
type: 'info',
|
||||
category: 'system',
|
||||
priority: 'medium',
|
||||
title: 'Subiendo datos de ventas',
|
||||
message: 'Subiendo historial de ventas al sistema para entrenamiento de IA...',
|
||||
source: 'onboarding'
|
||||
});
|
||||
|
||||
// TODO: Implement bulk sales record creation from file
|
||||
// For now, simulate success
|
||||
const importSuccess = true;
|
||||
|
||||
if (importSuccess) {
|
||||
salesImportResult = {
|
||||
records_created: processingResults.total_records,
|
||||
success: true,
|
||||
imported: true
|
||||
};
|
||||
|
||||
createAlert({
|
||||
type: 'success',
|
||||
category: 'system',
|
||||
priority: 'medium',
|
||||
title: 'Datos de ventas subidos',
|
||||
message: `Se subieron ${processingResults.total_records} registros de ventas al sistema exitosamente.`,
|
||||
source: 'onboarding'
|
||||
});
|
||||
} else {
|
||||
throw new Error('Failed to upload sales data');
|
||||
}
|
||||
} catch (salesError) {
|
||||
console.error('Error uploading sales data:', salesError);
|
||||
createAlert({
|
||||
type: 'error',
|
||||
category: 'system',
|
||||
priority: 'high',
|
||||
title: 'Error al subir datos de ventas',
|
||||
message: 'El inventario se creó correctamente, pero hubo un problema al subir los datos de ventas. Esto es requerido para el entrenamiento de IA.',
|
||||
source: 'onboarding'
|
||||
});
|
||||
|
||||
// Set failed result
|
||||
salesImportResult = {
|
||||
records_created: 0,
|
||||
success: false,
|
||||
error: salesError instanceof Error ? salesError.message : 'Error uploading sales data'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Update the step data with created inventory and sales import result
|
||||
console.log('InventorySetup - Updating step data with salesImportResult:', salesImportResult);
|
||||
const updatedData = {
|
||||
...data,
|
||||
inventoryItems: items,
|
||||
inventoryConfigured: true,
|
||||
inventoryCreated: true, // Mark as created to prevent duplicate calls
|
||||
inventoryMapping: inventoryMapping,
|
||||
createdInventoryItems: createdItems,
|
||||
salesImportResult: salesImportResult
|
||||
};
|
||||
console.log('InventorySetup - updatedData:', updatedData);
|
||||
onDataChange(updatedData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating inventory:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error al crear inventario';
|
||||
@@ -339,17 +306,34 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const lastItemsRef = useRef(items);
|
||||
const lastIsCreatingRef = useRef(isCreating);
|
||||
|
||||
useEffect(() => {
|
||||
const hasValidStock = items.length > 0 && items.every(item =>
|
||||
item.min_stock >= 0 && item.max_stock > item.min_stock
|
||||
);
|
||||
|
||||
onDataChange({
|
||||
...data,
|
||||
inventoryItems: items,
|
||||
inventoryConfigured: hasValidStock && !isCreating
|
||||
});
|
||||
}, [items, isCreating]);
|
||||
// Only update if items or isCreating actually changed
|
||||
if (JSON.stringify(items) !== JSON.stringify(lastItemsRef.current) || isCreating !== lastIsCreatingRef.current) {
|
||||
lastItemsRef.current = items;
|
||||
lastIsCreatingRef.current = isCreating;
|
||||
|
||||
const hasValidStock = items.length > 0 && items.every(item =>
|
||||
item.min_stock >= 0 && item.max_stock > item.min_stock
|
||||
);
|
||||
|
||||
const stepData = {
|
||||
inventoryItems: items,
|
||||
inventoryConfigured: hasValidStock && !isCreating
|
||||
};
|
||||
|
||||
// Update component props
|
||||
onDataChange({
|
||||
...data,
|
||||
...stepData
|
||||
});
|
||||
|
||||
// Update business hooks data
|
||||
updateStepData('inventory', stepData);
|
||||
}
|
||||
}, [items, isCreating]); // Only depend on items and isCreating
|
||||
|
||||
// Auto-create inventory when step is completed (when user clicks Next)
|
||||
useEffect(() => {
|
||||
@@ -358,11 +342,12 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
|
||||
);
|
||||
|
||||
// If inventory is configured but not yet created in backend, create it automatically
|
||||
if (hasValidStock && !data.inventoryCreated && !isCreating) {
|
||||
if (hasValidStock && !data.inventoryCreated && !isCreating && !inventoryCreationAttempted) {
|
||||
console.log('InventorySetup - Auto-creating inventory on step completion');
|
||||
setInventoryCreationAttempted(true);
|
||||
handleCreateInventory();
|
||||
}
|
||||
}, [data.inventoryCreated, items, isCreating]);
|
||||
}, [data.inventoryCreated, items, isCreating, inventoryCreationAttempted]);
|
||||
|
||||
const handleAddItem = () => {
|
||||
const newItem: InventoryItem = {
|
||||
|
||||
@@ -168,14 +168,13 @@ export const ReviewStep: React.FC<OnboardingStepProps> = ({
|
||||
approvedProductsCount: approvedProducts.length
|
||||
});
|
||||
onDataChange({
|
||||
...data,
|
||||
detectedProducts: products,
|
||||
approvedProducts,
|
||||
reviewCompleted
|
||||
});
|
||||
dataChangeRef.current = currentState;
|
||||
}
|
||||
}, [products, approvedProducts, reviewCompleted]);
|
||||
}, [products, approvedProducts, reviewCompleted, onDataChange]);
|
||||
|
||||
// Handle review completion alert separately
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user