Integrate categorization & stock steps with Tour system

- Updated WizardContext with new state management:
  - Added categorizedProducts and productsWithStock state
  - Added categorizationCompleted and stockEntryCompleted flags
  - Implemented updateCategorizedProducts() and updateProductsWithStock() methods
  - Updated getVisibleSteps() to include new steps in AI-assisted path

- Integrated ProductCategorizationStep and InitialStockEntryStep into UnifiedOnboardingWizard:
  - Added conditional rendering based on AI analysis completion
  - Wired up state management for both steps
  - Added intermediate update handlers

- Integrated Tour system at app level:
  - Added TourProvider to App.tsx context hierarchy
  - Added Tour component for rendering active tours
  - Added TourButton to Sidebar navigation
  - Tour button visible when sidebar is expanded

This completes the Phase 6.5 integration and sets up the guided tour infrastructure.
This commit is contained in:
Claude
2025-11-06 13:07:01 +00:00
parent a812291df6
commit 63d100f1b3
4 changed files with 71 additions and 1 deletions

View File

@@ -11,8 +11,10 @@ import { ThemeProvider } from './contexts/ThemeContext';
import { AuthProvider } from './contexts/AuthContext'; import { AuthProvider } from './contexts/AuthContext';
import { SSEProvider } from './contexts/SSEContext'; import { SSEProvider } from './contexts/SSEContext';
import { SubscriptionEventsProvider } from './contexts/SubscriptionEventsContext'; import { SubscriptionEventsProvider } from './contexts/SubscriptionEventsContext';
import { TourProvider } from './contexts/TourContext';
import GlobalSubscriptionHandler from './components/auth/GlobalSubscriptionHandler'; import GlobalSubscriptionHandler from './components/auth/GlobalSubscriptionHandler';
import { CookieBanner } from './components/ui/CookieConsent'; import { CookieBanner } from './components/ui/CookieConsent';
import { Tour } from './components/ui/Tour';
import i18n from './i18n'; import i18n from './i18n';
const queryClient = new QueryClient({ const queryClient = new QueryClient({
@@ -65,7 +67,10 @@ function App() {
<AuthProvider> <AuthProvider>
<SSEProvider> <SSEProvider>
<SubscriptionEventsProvider> <SubscriptionEventsProvider>
<AppContent /> <TourProvider>
<AppContent />
<Tour />
</TourProvider>
</SubscriptionEventsProvider> </SubscriptionEventsProvider>
</SSEProvider> </SSEProvider>
</AuthProvider> </AuthProvider>

View File

@@ -13,6 +13,8 @@ import {
DataSourceChoiceStep, DataSourceChoiceStep,
RegisterTenantStep, RegisterTenantStep,
UploadSalesDataStep, UploadSalesDataStep,
ProductCategorizationStep,
InitialStockEntryStep,
ProductionProcessesStep, ProductionProcessesStep,
MLTrainingStep, MLTrainingStep,
CompletionStep CompletionStep
@@ -89,6 +91,22 @@ const OnboardingWizardContent: React.FC = () => {
isConditional: true, isConditional: true,
condition: (ctx) => ctx.state.dataSource === 'ai-assisted', condition: (ctx) => ctx.state.dataSource === 'ai-assisted',
}, },
{
id: 'product-categorization',
title: t('onboarding:steps.categorization.title', 'Categorizar Productos'),
description: t('onboarding:steps.categorization.description', 'Clasifica ingredientes vs productos'),
component: ProductCategorizationStep,
isConditional: true,
condition: (ctx) => ctx.state.dataSource === 'ai-assisted' && ctx.state.aiAnalysisComplete,
},
{
id: 'initial-stock-entry',
title: t('onboarding:steps.stock.title', 'Niveles de Stock'),
description: t('onboarding:steps.stock.description', 'Cantidades iniciales'),
component: InitialStockEntryStep,
isConditional: true,
condition: (ctx) => ctx.state.dataSource === 'ai-assisted' && ctx.state.categorizationCompleted,
},
// Phase 2b: Core Data Entry // Phase 2b: Core Data Entry
{ {
id: 'suppliers-setup', id: 'suppliers-setup',
@@ -322,6 +340,14 @@ const OnboardingWizardContent: React.FC = () => {
wizardContext.updateAISuggestions(data.aiSuggestions); wizardContext.updateAISuggestions(data.aiSuggestions);
wizardContext.setAIAnalysisComplete(true); wizardContext.setAIAnalysisComplete(true);
} }
if (currentStep.id === 'product-categorization' && data?.categorizedProducts) {
wizardContext.updateCategorizedProducts(data.categorizedProducts);
wizardContext.markStepComplete('categorizationCompleted');
}
if (currentStep.id === 'initial-stock-entry' && data?.productsWithStock) {
wizardContext.updateProductsWithStock(data.productsWithStock);
wizardContext.markStepComplete('stockEntryCompleted');
}
if (currentStep.id === 'inventory-setup') { if (currentStep.id === 'inventory-setup') {
wizardContext.markStepComplete('inventoryCompleted'); wizardContext.markStepComplete('inventoryCompleted');
} }
@@ -381,6 +407,12 @@ const OnboardingWizardContent: React.FC = () => {
if (currentStep.id === 'data-source-choice' && data?.dataSource) { if (currentStep.id === 'data-source-choice' && data?.dataSource) {
wizardContext.updateDataSource(data.dataSource as DataSource); wizardContext.updateDataSource(data.dataSource as DataSource);
} }
if (currentStep.id === 'product-categorization' && data?.categorizedProducts) {
wizardContext.updateCategorizedProducts(data.categorizedProducts);
}
if (currentStep.id === 'initial-stock-entry' && data?.productsWithStock) {
wizardContext.updateProductsWithStock(data.productsWithStock);
}
}; };
// Show loading state // Show loading state

View File

@@ -23,8 +23,12 @@ export interface WizardState {
uploadedFileSize?: number; uploadedFileSize?: number;
aiSuggestions: AISuggestion[]; aiSuggestions: AISuggestion[];
aiAnalysisComplete: boolean; aiAnalysisComplete: boolean;
categorizedProducts?: any[]; // Products with type classification
productsWithStock?: any[]; // Products with initial stock levels
// Setup Progress // Setup Progress
categorizationCompleted: boolean;
stockEntryCompleted: boolean;
suppliersCompleted: boolean; suppliersCompleted: boolean;
inventoryCompleted: boolean; inventoryCompleted: boolean;
recipesCompleted: boolean; recipesCompleted: boolean;
@@ -47,6 +51,8 @@ export interface WizardContextValue {
updateDataSource: (source: DataSource) => void; updateDataSource: (source: DataSource) => void;
updateAISuggestions: (suggestions: AISuggestion[]) => void; updateAISuggestions: (suggestions: AISuggestion[]) => void;
setAIAnalysisComplete: (complete: boolean) => void; setAIAnalysisComplete: (complete: boolean) => void;
updateCategorizedProducts: (products: any[]) => void;
updateProductsWithStock: (products: any[]) => void;
markStepComplete: (step: keyof WizardState) => void; markStepComplete: (step: keyof WizardState) => void;
getVisibleSteps: () => string[]; getVisibleSteps: () => string[];
shouldShowStep: (stepId: string) => boolean; shouldShowStep: (stepId: string) => boolean;
@@ -58,6 +64,10 @@ const initialState: WizardState = {
dataSource: null, dataSource: null,
aiSuggestions: [], aiSuggestions: [],
aiAnalysisComplete: false, aiAnalysisComplete: false,
categorizedProducts: undefined,
productsWithStock: undefined,
categorizationCompleted: false,
stockEntryCompleted: false,
suppliersCompleted: false, suppliersCompleted: false,
inventoryCompleted: false, inventoryCompleted: false,
recipesCompleted: false, recipesCompleted: false,
@@ -121,6 +131,14 @@ export const WizardProvider: React.FC<WizardProviderProps> = ({
setState(prev => ({ ...prev, aiAnalysisComplete: complete })); setState(prev => ({ ...prev, aiAnalysisComplete: complete }));
}; };
const updateCategorizedProducts = (products: any[]) => {
setState(prev => ({ ...prev, categorizedProducts: products }));
};
const updateProductsWithStock = (products: any[]) => {
setState(prev => ({ ...prev, productsWithStock: products }));
};
const markStepComplete = (step: keyof WizardState) => { const markStepComplete = (step: keyof WizardState) => {
setState(prev => ({ ...prev, [step]: true })); setState(prev => ({ ...prev, [step]: true }));
}; };
@@ -154,6 +172,11 @@ export const WizardProvider: React.FC<WizardProviderProps> = ({
if (state.aiAnalysisComplete) { if (state.aiAnalysisComplete) {
steps.push('review-suggestions'); steps.push('review-suggestions');
steps.push('product-categorization');
}
if (state.categorizationCompleted) {
steps.push('initial-stock-entry');
} }
} }
@@ -210,6 +233,8 @@ export const WizardProvider: React.FC<WizardProviderProps> = ({
updateDataSource, updateDataSource,
updateAISuggestions, updateAISuggestions,
setAIAnalysisComplete, setAIAnalysisComplete,
updateCategorizedProducts,
updateProductsWithStock,
markStepComplete, markStepComplete,
getVisibleSteps, getVisibleSteps,
shouldShowStep, shouldShowStep,

View File

@@ -12,6 +12,7 @@ import { Button } from '../../ui';
import { Badge } from '../../ui'; import { Badge } from '../../ui';
import { Tooltip } from '../../ui'; import { Tooltip } from '../../ui';
import { Avatar } from '../../ui'; import { Avatar } from '../../ui';
import { TourButton } from '../../ui/Tour/TourButton';
import { import {
LayoutDashboard, LayoutDashboard,
Package, Package,
@@ -811,6 +812,13 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
</div> </div>
)} )}
{/* Tour Button */}
{!isCollapsed && (
<div className="px-4 pb-2">
<TourButton variant="button" />
</div>
)}
{/* Navigation */} {/* Navigation */}
<nav className={clsx('flex-1 overflow-y-auto overflow-x-hidden', isCollapsed ? 'px-1 py-4' : 'p-4')}> <nav className={clsx('flex-1 overflow-y-auto overflow-x-hidden', isCollapsed ? 'px-1 py-4' : 'p-4')}>
<ul className={clsx(isCollapsed ? 'space-y-1 flex flex-col items-center' : 'space-y-2')}> <ul className={clsx(isCollapsed ? 'space-y-1 flex flex-col items-center' : 'space-y-2')}>