306 lines
9.7 KiB
TypeScript
306 lines
9.7 KiB
TypeScript
|
|
/**
|
||
|
|
* Main onboarding hook - orchestrates all focused onboarding hooks
|
||
|
|
* This is the primary hook that components should use
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { useCallback } from 'react';
|
||
|
|
import { useNavigate } from 'react-router-dom';
|
||
|
|
import { useAuthUser } from '../../../stores/auth.store';
|
||
|
|
import { useCurrentTenant } from '../../../stores';
|
||
|
|
import { useOnboardingFlow } from './useOnboardingFlow';
|
||
|
|
import { useOnboardingData } from './useOnboardingData';
|
||
|
|
import { useTenantCreation } from './useTenantCreation';
|
||
|
|
import { useSalesProcessing } from './useSalesProcessing';
|
||
|
|
import { useInventorySetup } from './useInventorySetup';
|
||
|
|
import { useTrainingOrchestration } from './useTrainingOrchestration';
|
||
|
|
import type {
|
||
|
|
OnboardingData,
|
||
|
|
ProgressCallback,
|
||
|
|
ProductSuggestionResponse
|
||
|
|
} from './types';
|
||
|
|
import type { BakeryRegistration } from '../../../api';
|
||
|
|
|
||
|
|
interface OnboardingActions {
|
||
|
|
// Navigation
|
||
|
|
nextStep: () => boolean;
|
||
|
|
previousStep: () => boolean;
|
||
|
|
goToStep: (stepIndex: number) => boolean;
|
||
|
|
|
||
|
|
// Data Management
|
||
|
|
updateStepData: (stepId: string, data: Partial<OnboardingData>) => void;
|
||
|
|
validateCurrentStep: () => string | null;
|
||
|
|
|
||
|
|
// Step-specific Actions
|
||
|
|
createTenant: (bakeryData: BakeryRegistration) => Promise<boolean>;
|
||
|
|
processSalesFile: (file: File, onProgress?: ProgressCallback) => Promise<boolean>;
|
||
|
|
generateInventorySuggestions: (productList: string[]) => Promise<ProductSuggestionResponse[] | null>;
|
||
|
|
createInventoryFromSuggestions: (suggestions: ProductSuggestionResponse[]) => Promise<boolean>;
|
||
|
|
importSalesData: (salesData: any, inventoryMapping: { [productName: string]: string }) => Promise<boolean>;
|
||
|
|
startTraining: (options?: {
|
||
|
|
products?: string[];
|
||
|
|
startDate?: string;
|
||
|
|
endDate?: string;
|
||
|
|
}) => Promise<boolean>;
|
||
|
|
|
||
|
|
// Completion
|
||
|
|
completeOnboarding: () => Promise<boolean>;
|
||
|
|
|
||
|
|
// Utilities
|
||
|
|
clearError: () => void;
|
||
|
|
reset: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const useOnboarding = () => {
|
||
|
|
const navigate = useNavigate();
|
||
|
|
const user = useAuthUser();
|
||
|
|
const currentTenant = useCurrentTenant();
|
||
|
|
|
||
|
|
// Focused hooks
|
||
|
|
const flow = useOnboardingFlow();
|
||
|
|
const data = useOnboardingData();
|
||
|
|
const tenantCreation = useTenantCreation();
|
||
|
|
const salesProcessing = useSalesProcessing();
|
||
|
|
const inventorySetup = useInventorySetup();
|
||
|
|
const trainingOrchestration = useTrainingOrchestration();
|
||
|
|
|
||
|
|
// Navigation actions
|
||
|
|
const nextStep = useCallback((): boolean => {
|
||
|
|
const validation = validateCurrentStep();
|
||
|
|
if (validation) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (flow.nextStep()) {
|
||
|
|
flow.markStepCompleted(flow.currentStep - 1);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}, [flow]);
|
||
|
|
|
||
|
|
const previousStep = useCallback((): boolean => {
|
||
|
|
return flow.previousStep();
|
||
|
|
}, [flow]);
|
||
|
|
|
||
|
|
const goToStep = useCallback((stepIndex: number): boolean => {
|
||
|
|
return flow.goToStep(stepIndex);
|
||
|
|
}, [flow]);
|
||
|
|
|
||
|
|
// Data management
|
||
|
|
const updateStepData = useCallback((stepId: string, stepData: Partial<OnboardingData>) => {
|
||
|
|
data.updateStepData(stepId, stepData);
|
||
|
|
}, [data]);
|
||
|
|
|
||
|
|
const validateCurrentStep = useCallback((): string | null => {
|
||
|
|
const currentStep = flow.getCurrentStep();
|
||
|
|
const validationResult = data.validateStep(currentStep.id);
|
||
|
|
|
||
|
|
// Also check for specific step validations
|
||
|
|
if (validationResult) return validationResult;
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}, [flow, data]);
|
||
|
|
|
||
|
|
// Step-specific actions
|
||
|
|
const createTenant = useCallback(async (bakeryData: BakeryRegistration): Promise<boolean> => {
|
||
|
|
const success = await tenantCreation.createTenant(bakeryData);
|
||
|
|
if (success) {
|
||
|
|
updateStepData('setup', { bakery: bakeryData });
|
||
|
|
}
|
||
|
|
return success;
|
||
|
|
}, [tenantCreation, updateStepData]);
|
||
|
|
|
||
|
|
const processSalesFile = useCallback(async (
|
||
|
|
file: File,
|
||
|
|
onProgress?: ProgressCallback
|
||
|
|
): Promise<boolean> => {
|
||
|
|
const result = await salesProcessing.processFile(file, onProgress);
|
||
|
|
if (result.success) {
|
||
|
|
updateStepData('data-processing', {
|
||
|
|
files: { salesData: file },
|
||
|
|
processingStage: 'completed',
|
||
|
|
processingResults: result.validationResults,
|
||
|
|
suggestions: result.suggestions || []
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return result.success;
|
||
|
|
}, [salesProcessing, updateStepData]);
|
||
|
|
|
||
|
|
const generateInventorySuggestions = useCallback(async (
|
||
|
|
productList: string[]
|
||
|
|
): Promise<ProductSuggestionResponse[] | null> => {
|
||
|
|
return salesProcessing.generateSuggestions(productList);
|
||
|
|
}, [salesProcessing]);
|
||
|
|
|
||
|
|
const createInventoryFromSuggestions = useCallback(async (
|
||
|
|
suggestions: ProductSuggestionResponse[]
|
||
|
|
): Promise<boolean> => {
|
||
|
|
const result = await inventorySetup.createInventoryFromSuggestions(suggestions);
|
||
|
|
if (result.success) {
|
||
|
|
updateStepData('inventory', {
|
||
|
|
inventoryItems: result.createdItems,
|
||
|
|
inventoryMapping: result.inventoryMapping,
|
||
|
|
inventoryConfigured: true,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return result.success;
|
||
|
|
}, [inventorySetup, updateStepData]);
|
||
|
|
|
||
|
|
const importSalesData = useCallback(async (
|
||
|
|
salesData: any,
|
||
|
|
inventoryMapping: { [productName: string]: string }
|
||
|
|
): Promise<boolean> => {
|
||
|
|
const result = await inventorySetup.importSalesData(salesData, inventoryMapping);
|
||
|
|
if (result.success) {
|
||
|
|
updateStepData('inventory', {
|
||
|
|
salesImportResult: {
|
||
|
|
success: result.success,
|
||
|
|
imported: true,
|
||
|
|
records_created: result.recordsCreated,
|
||
|
|
message: result.message,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return result.success;
|
||
|
|
}, [inventorySetup, updateStepData]);
|
||
|
|
|
||
|
|
const startTraining = useCallback(async (options?: {
|
||
|
|
products?: string[];
|
||
|
|
startDate?: string;
|
||
|
|
endDate?: string;
|
||
|
|
}): Promise<boolean> => {
|
||
|
|
// First validate training data requirements
|
||
|
|
const allStepData = data.getAllStepData();
|
||
|
|
const validation = await trainingOrchestration.validateTrainingData(allStepData);
|
||
|
|
|
||
|
|
if (!validation.isValid) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const success = await trainingOrchestration.startTraining(options);
|
||
|
|
if (success) {
|
||
|
|
updateStepData('ml-training', {
|
||
|
|
trainingStatus: 'training',
|
||
|
|
trainingJob: trainingOrchestration.job,
|
||
|
|
trainingLogs: trainingOrchestration.logs,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return success;
|
||
|
|
}, [trainingOrchestration, data, updateStepData]);
|
||
|
|
|
||
|
|
const completeOnboarding = useCallback(async (): Promise<boolean> => {
|
||
|
|
// Mark final completion
|
||
|
|
updateStepData('completion', {
|
||
|
|
completionStats: {
|
||
|
|
totalProducts: data.data.processingResults?.unique_products || 0,
|
||
|
|
inventoryItems: data.data.inventoryItems?.length || 0,
|
||
|
|
suppliersConfigured: data.data.suppliers?.length || 0,
|
||
|
|
mlModelAccuracy: data.data.trainingMetrics?.accuracy || 0,
|
||
|
|
estimatedTimeSaved: '2-3 horas por día',
|
||
|
|
completionScore: 95,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
flow.markStepCompleted(flow.steps.length - 1);
|
||
|
|
|
||
|
|
// Navigate to dashboard after completion
|
||
|
|
setTimeout(() => {
|
||
|
|
navigate('/app/dashboard');
|
||
|
|
}, 2000);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}, [data, flow, navigate, updateStepData]);
|
||
|
|
|
||
|
|
// Utilities
|
||
|
|
const clearError = useCallback(() => {
|
||
|
|
data.clearError();
|
||
|
|
tenantCreation.clearError();
|
||
|
|
salesProcessing.clearError();
|
||
|
|
inventorySetup.clearError();
|
||
|
|
trainingOrchestration.clearError();
|
||
|
|
}, [data, tenantCreation, salesProcessing, inventorySetup, trainingOrchestration]);
|
||
|
|
|
||
|
|
const reset = useCallback(() => {
|
||
|
|
flow.resetFlow();
|
||
|
|
data.resetData();
|
||
|
|
tenantCreation.reset();
|
||
|
|
salesProcessing.reset();
|
||
|
|
inventorySetup.reset();
|
||
|
|
trainingOrchestration.reset();
|
||
|
|
}, [flow, data, tenantCreation, salesProcessing, inventorySetup, trainingOrchestration]);
|
||
|
|
|
||
|
|
// Determine overall loading and error state
|
||
|
|
const isLoading = tenantCreation.isLoading ||
|
||
|
|
salesProcessing.isLoading ||
|
||
|
|
inventorySetup.isLoading ||
|
||
|
|
trainingOrchestration.isLoading;
|
||
|
|
|
||
|
|
const error = data.error?.message ||
|
||
|
|
tenantCreation.error ||
|
||
|
|
salesProcessing.error ||
|
||
|
|
inventorySetup.error ||
|
||
|
|
trainingOrchestration.error;
|
||
|
|
|
||
|
|
return {
|
||
|
|
// State from flow management
|
||
|
|
currentStep: flow.currentStep,
|
||
|
|
steps: flow.steps,
|
||
|
|
progress: flow.getProgress(),
|
||
|
|
|
||
|
|
// State from data management
|
||
|
|
data: data.data,
|
||
|
|
allStepData: data.getAllStepData(),
|
||
|
|
|
||
|
|
// State from individual hooks
|
||
|
|
tenantCreation: {
|
||
|
|
isLoading: tenantCreation.isLoading,
|
||
|
|
isSuccess: tenantCreation.isSuccess,
|
||
|
|
},
|
||
|
|
salesProcessing: {
|
||
|
|
stage: salesProcessing.stage,
|
||
|
|
progress: salesProcessing.progress,
|
||
|
|
currentMessage: salesProcessing.currentMessage,
|
||
|
|
validationResults: salesProcessing.validationResults,
|
||
|
|
suggestions: salesProcessing.suggestions,
|
||
|
|
},
|
||
|
|
inventorySetup: {
|
||
|
|
createdItems: inventorySetup.createdItems,
|
||
|
|
inventoryMapping: inventorySetup.inventoryMapping,
|
||
|
|
salesImportResult: inventorySetup.salesImportResult,
|
||
|
|
isInventoryConfigured: inventorySetup.isInventoryConfigured,
|
||
|
|
},
|
||
|
|
trainingOrchestration: {
|
||
|
|
status: trainingOrchestration.status,
|
||
|
|
progress: trainingOrchestration.progress,
|
||
|
|
currentStep: trainingOrchestration.currentStep,
|
||
|
|
estimatedTimeRemaining: trainingOrchestration.estimatedTimeRemaining,
|
||
|
|
job: trainingOrchestration.job,
|
||
|
|
logs: trainingOrchestration.logs,
|
||
|
|
metrics: trainingOrchestration.metrics,
|
||
|
|
},
|
||
|
|
|
||
|
|
// Overall state
|
||
|
|
isLoading,
|
||
|
|
error,
|
||
|
|
user,
|
||
|
|
currentTenant,
|
||
|
|
|
||
|
|
// Actions
|
||
|
|
nextStep,
|
||
|
|
previousStep,
|
||
|
|
goToStep,
|
||
|
|
updateStepData,
|
||
|
|
validateCurrentStep,
|
||
|
|
createTenant,
|
||
|
|
processSalesFile,
|
||
|
|
generateInventorySuggestions,
|
||
|
|
createInventoryFromSuggestions,
|
||
|
|
importSalesData,
|
||
|
|
startTraining,
|
||
|
|
completeOnboarding,
|
||
|
|
clearError,
|
||
|
|
reset,
|
||
|
|
} satisfies ReturnType<typeof useOnboardingFlow> &
|
||
|
|
ReturnType<typeof useOnboardingData> &
|
||
|
|
{ [key: string]: any } &
|
||
|
|
OnboardingActions;
|
||
|
|
};
|