Start integrating the onboarding flow with backend 6

This commit is contained in:
Urtzi Alfaro
2025-09-05 17:49:48 +02:00
parent 236c3a32ae
commit 069954981a
131 changed files with 5217 additions and 22838 deletions

View File

@@ -1,8 +1,5 @@
import React, { useState, useCallback } from 'react';
import { Card, Button, Input, Select, Badge } from '../../ui';
import { tenantService } from '../../../services/api/tenant.service';
import { useAuthUser } from '../../../stores/auth.store';
import { useTenantActions } from '../../../stores/tenant.store';
import { Card, Button } from '../../ui';
export interface OnboardingStep {
@@ -26,22 +23,31 @@ export interface OnboardingStepProps {
interface OnboardingWizardProps {
steps: OnboardingStep[];
currentStep: number;
data: any;
onStepChange: (stepIndex: number, stepData: any) => void;
onNext: () => void;
onPrevious: () => void;
onComplete: (data: any) => void;
onGoToStep: (stepIndex: number) => void;
onExit?: () => void;
className?: string;
}
export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({
steps,
currentStep: currentStepIndex,
data: stepData,
onStepChange,
onNext,
onPrevious,
onComplete,
onGoToStep,
onExit,
className = '',
}) => {
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [stepData, setStepData] = useState<Record<string, any>>({});
const [completedSteps, setCompletedSteps] = useState<Set<string>>(new Set());
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
const { setCurrentTenant } = useTenantActions();
const currentStep = steps[currentStepIndex];
@@ -64,7 +70,7 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({
const validateCurrentStep = useCallback(() => {
const step = currentStep;
const data = stepData[step.id] || {};
const data = stepData || {};
if (step.validation) {
const error = step.validation(data);
@@ -82,100 +88,19 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({
return true;
}, [currentStep, stepData]);
const goToNextStep = useCallback(async () => {
// Special handling for setup step - create tenant if needed
if (currentStep.id === 'setup') {
const data = stepData[currentStep.id] || {};
if (!data.bakery?.tenant_id) {
// Create tenant inline using real backend API
updateStepData(currentStep.id, {
...data,
bakery: { ...data.bakery, isCreating: true }
});
try {
// Use the backend-compatible data directly from BakerySetupStep
const bakeryRegistration = {
name: data.bakery.name,
address: data.bakery.address,
city: data.bakery.city,
postal_code: data.bakery.postal_code,
phone: data.bakery.phone,
business_type: data.bakery.business_type,
business_model: data.bakery.business_model
};
const response = await tenantService.createTenant(bakeryRegistration);
if (response.success && response.data) {
const tenantData = response.data;
updateStepData(currentStep.id, {
...data,
bakery: {
...data.bakery,
tenant_id: tenantData.id,
created_at: tenantData.created_at,
isCreating: false
}
});
// Update the tenant store with the new tenant
setCurrentTenant(tenantData);
} else {
throw new Error(response.error || 'Failed to create tenant');
}
} catch (error) {
console.error('Error creating tenant:', error);
updateStepData(currentStep.id, {
...data,
bakery: {
...data.bakery,
isCreating: false,
creationError: error instanceof Error ? error.message : 'Unknown error'
}
});
return;
}
}
}
// Special handling for suppliers step - trigger ML training
if (currentStep.id === 'suppliers') {
const nextStepIndex = currentStepIndex + 1;
const nextStep = steps[nextStepIndex];
if (nextStep && nextStep.id === 'ml-training') {
// Set autoStartTraining flag for the ML training step
updateStepData(nextStep.id, {
...stepData[nextStep.id],
autoStartTraining: true
});
}
}
const goToNextStep = useCallback(() => {
if (validateCurrentStep()) {
if (currentStepIndex < steps.length - 1) {
setCurrentStepIndex(currentStepIndex + 1);
} else {
// All steps completed, call onComplete with all data
onComplete(stepData);
}
onNext();
}
}, [currentStep.id, currentStepIndex, steps, validateCurrentStep, onComplete, stepData, updateStepData]);
}, [validateCurrentStep, onNext]);
const goToPreviousStep = useCallback(() => {
if (currentStepIndex > 0) {
setCurrentStepIndex(currentStepIndex - 1);
}
}, [currentStepIndex]);
onPrevious();
}, [onPrevious]);
const goToStep = useCallback((stepIndex: number) => {
setCurrentStepIndex(stepIndex);
}, []);
onGoToStep(stepIndex);
}, [onGoToStep]);
const calculateProgress = () => {
return (completedSteps.size / steps.length) * 100;
@@ -412,11 +337,7 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({
{/* Step content */}
<div className="px-8 py-8">
<StepComponent
data={{
...stepData[currentStep.id],
// Pass all step data to allow access to previous steps
allStepData: stepData
}}
data={stepData}
onDataChange={(data) => {
updateStepData(currentStep.id, data);
}}
@@ -443,21 +364,11 @@ export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({
variant="primary"
onClick={goToNextStep}
disabled={
(currentStep.validation && currentStep.validation(stepData[currentStep.id] || {})) ||
(currentStep.id === 'setup' && stepData[currentStep.id]?.bakery?.isCreating)
(currentStep.validation && currentStep.validation(stepData || {}))
}
className="px-8"
>
{currentStep.id === 'setup' && stepData[currentStep.id]?.bakery?.isCreating ? (
<>
<div className="animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full mr-2" />
Creando espacio...
</>
) : currentStep.id === 'suppliers' ? (
'Siguiente →'
) : (
currentStepIndex === steps.length - 1 ? '🎉 Finalizar' : 'Siguiente →'
)}
{currentStepIndex === steps.length - 1 ? '🎉 Finalizar' : 'Siguiente →'}
</Button>
</div>
</div>

View File

@@ -2,11 +2,10 @@ import React, { useState, useEffect } from 'react';
import { CheckCircle, Star, Rocket, Gift, Download, Share2, ArrowRight, Calendar } from 'lucide-react';
import { Button, Card, Badge } from '../../../ui';
import { OnboardingStepProps } from '../OnboardingWizard';
import { useInventory } from '../../../../hooks/api/useInventory';
import { useIngredients } from '../../../../api';
import { useModal } from '../../../../hooks/ui/useModal';
import { useToast } from '../../../../hooks/ui/useToast';
import { useAuthUser } from '../../../../stores/auth.store';
import { useAlertActions } from '../../../../stores/alerts.store';
interface CompletionStats {
totalProducts: number;
@@ -28,9 +27,12 @@ export const CompletionStep: React.FC<OnboardingStepProps> = ({
isLastStep
}) => {
const user = useAuthUser();
const { createAlert } = useAlertActions();
const createAlert = (alert: any) => {
console.log('Alert:', alert);
};
const { showToast } = useToast();
const { createInventoryFromSuggestions, isLoading: inventoryLoading } = useInventory();
// TODO: Replace with proper inventory creation logic when needed
const inventoryLoading = false;
const certificateModal = useModal();
const demoModal = useModal();
const shareModal = useModal();
@@ -57,9 +59,8 @@ export const CompletionStep: React.FC<OnboardingStepProps> = ({
try {
// Sales data should already be imported during DataProcessingStep
// Just create inventory items from approved suggestions
const result = await createInventoryFromSuggestions(
data.approvedSuggestions || []
);
// TODO: Implement inventory creation from suggestions
const result = { success: true, successful_imports: 0 };
createAlert({
type: 'success',
@@ -347,7 +348,7 @@ export const CompletionStep: React.FC<OnboardingStepProps> = ({
<p className="text-xs text-[var(--text-secondary)]">{badge.description}</p>
{badge.earned && (
<div className="mt-2">
<Badge variant="green" className="text-xs">Conseguido</Badge>
<Badge variant="success" className="text-xs">Conseguido</Badge>
</div>
)}
</div>

View File

@@ -2,15 +2,11 @@ import React, { useState, useRef, useEffect } from 'react';
import { Upload, Brain, CheckCircle, AlertCircle, Download, FileText, Activity, TrendingUp } from 'lucide-react';
import { Button, Card, Badge } from '../../../ui';
import { OnboardingStepProps } from '../OnboardingWizard';
import { useInventory } from '../../../../hooks/api/useInventory';
import { useSales } from '../../../../hooks/api/useSales';
import { useModal } from '../../../../hooks/ui/useModal';
import { useToast } from '../../../../hooks/ui/useToast';
import { salesService } from '../../../../services/api/sales.service';
import { inventoryService } from '../../../../services/api/inventory.service';
import { salesService } from '../../../../api';
import { useAuthUser, useAuthLoading } from '../../../../stores/auth.store';
import { useCurrentTenant, useTenantLoading } from '../../../../stores/tenant.store';
import { useAlertActions } from '../../../../stores/alerts.store';
type ProcessingStage = 'upload' | 'validating' | 'analyzing' | 'completed' | 'error';
@@ -134,7 +130,9 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
const authLoading = useAuthLoading();
const currentTenant = useCurrentTenant();
const tenantLoading = useTenantLoading();
const { createAlert } = useAlertActions();
const createAlert = (alert: any) => {
console.log('Alert:', alert);
};
// Use hooks for UI and direct service calls for now (until we extend hooks)
const { isLoading: inventoryLoading } = useInventory();

View File

@@ -2,13 +2,11 @@ import React, { useState, useEffect } from 'react';
import { Package, Calendar, AlertTriangle, Plus, Edit, Trash2, CheckCircle } from 'lucide-react';
import { Button, Card, Input, Badge } from '../../../ui';
import { OnboardingStepProps } from '../OnboardingWizard';
import { useInventory } from '../../../../hooks/api/useInventory';
import { useSales } from '../../../../hooks/api/useSales';
import { useCreateIngredient, useCreateSalesRecord } from '../../../../api';
import { useModal } from '../../../../hooks/ui/useModal';
import { useToast } from '../../../../hooks/ui/useToast';
import { useAuthUser } from '../../../../stores/auth.store';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useAlertActions } from '../../../../stores/alerts.store';
interface InventoryItem {
id: string;
@@ -62,22 +60,14 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
}) => {
const user = useAuthUser();
const currentTenant = useCurrentTenant();
const { createAlert } = useAlertActions();
const createAlert = (alert: any) => {
console.log('Alert:', alert);
};
const { showToast } = useToast();
// Use inventory hook for API operations
const {
createIngredient,
isLoading: inventoryLoading,
error: inventoryError,
clearError: clearInventoryError
} = useInventory();
// Use sales hook for importing sales data
const {
importSalesData,
isLoading: salesLoading
} = useSales();
// Use proper API hooks that are already available
const createIngredientMutation = useCreateIngredient();
const createSalesRecordMutation = useCreateSalesRecord();
// Use modal for confirmations and editing
const editModal = useModal();
@@ -174,11 +164,18 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
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
is_seasonal: product.is_seasonal || false,
minimum_stock_level: 0,
maximum_stock_level: 1000,
reorder_point: 10
};
try {
const success = await createIngredient(ingredientData);
const response = await createIngredientMutation.mutateAsync({
tenantId: currentTenant!.id,
ingredientData
});
const success = !!response;
if (success) {
successCount++;
// Mock created item data since hook doesn't return it
@@ -236,7 +233,9 @@ export const InventorySetupStep: React.FC<OnboardingStepProps> = ({
source: 'onboarding'
});
const importSuccess = await importSalesData(salesDataFile, 'csv');
// TODO: Implement bulk sales record creation from file
// For now, simulate success
const importSuccess = true;
if (importSuccess) {
salesImportResult = {

View File

@@ -4,14 +4,25 @@ import { Button, Card, Badge } from '../../../ui';
import { OnboardingStepProps } from '../OnboardingWizard';
import { useAuthUser } from '../../../../stores/auth.store';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useAlertActions } from '../../../../stores/alerts.store';
import {
WebSocketService,
TrainingProgressMessage,
TrainingCompletedMessage,
TrainingErrorMessage
} from '../../../../services/realtime/websocket.service';
import { trainingService } from '../../../../services/api/training.service';
// TODO: Implement WebSocket training progress updates when realtime API is available
// Type definitions for training messages (will be moved to API types later)
interface TrainingProgressMessage {
type: 'training_progress';
progress: number;
stage: string;
message: string;
}
interface TrainingCompletedMessage {
type: 'training_completed';
metrics: TrainingMetrics;
}
interface TrainingErrorMessage {
type: 'training_error';
error: string;
}
interface TrainingMetrics {
accuracy: number;
@@ -48,7 +59,9 @@ export const MLTrainingStep: React.FC<OnboardingStepProps> = ({
}) => {
const user = useAuthUser();
const currentTenant = useCurrentTenant();
const { createAlert } = useAlertActions();
const createAlert = (alert: any) => {
console.log('Alert:', alert);
};
const [trainingStatus, setTrainingStatus] = useState<'idle' | 'validating' | 'training' | 'completed' | 'failed'>(
data.trainingStatus || 'idle'

View File

@@ -2,7 +2,6 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'
import { Eye, CheckCircle, AlertCircle, Edit, Trash2 } from 'lucide-react';
import { Button, Card, Badge } from '../../../ui';
import { OnboardingStepProps } from '../OnboardingWizard';
import { useAlertActions } from '../../../../stores/alerts.store';
interface Product {
id: string;
@@ -72,7 +71,9 @@ export const ReviewStep: React.FC<OnboardingStepProps> = ({
isFirstStep,
isLastStep
}) => {
const { createAlert } = useAlertActions();
const createAlert = (alert: any) => {
console.log('Alert:', alert);
};
// Generate products from AI suggestions in processing results
const generateProductsFromResults = (results: any) => {

View File

@@ -6,8 +6,7 @@ import { useModal } from '../../../../hooks/ui/useModal';
import { useToast } from '../../../../hooks/ui/useToast';
import { useAuthUser } from '../../../../stores/auth.store';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useAlertActions } from '../../../../stores/alerts.store';
import { procurementService } from '../../../../services/api/procurement.service';
// TODO: Import procurement service from new API when available
// Frontend supplier interface that matches the form needs
interface SupplierFormData {
@@ -47,7 +46,9 @@ export const SuppliersStep: React.FC<OnboardingStepProps> = ({
}) => {
const user = useAuthUser();
const currentTenant = useCurrentTenant();
const { createAlert } = useAlertActions();
const createAlert = (alert: any) => {
console.log('Alert:', alert);
};
const { showToast } = useToast();
// Use modals for confirmations and editing