diff --git a/frontend/src/components/domain/onboarding/steps/CompletionStep.tsx b/frontend/src/components/domain/onboarding/steps/CompletionStep.tsx index b4b57ad9..806bd0f2 100644 --- a/frontend/src/components/domain/onboarding/steps/CompletionStep.tsx +++ b/frontend/src/components/domain/onboarding/steps/CompletionStep.tsx @@ -22,6 +22,11 @@ export const CompletionStep: React.FC = ({ navigate('/app'); }; + const handleContinueSetup = () => { + onComplete({ redirectTo: '/app/setup' }); + navigate('/app/setup'); + }; + return (
{/* Success Icon */} @@ -80,56 +85,96 @@ export const CompletionStep: React.FC = ({
+ {/* One More Thing - Setup Wizard CTA */} +
+
+
+ ✨ +
+
+

¡Una cosa más!

+

+ Para aprovechar al máximo el sistema, configura tus operaciones diarias: proveedores, inventario, recetas y estándares de calidad. +

+
+ + + + Toma solo 15-20 minutos +
+
+
+
+ {/* Next Steps */}
-

Próximos Pasos

-
-
-
- 1 +

Lo Que Configurarás

+
+
+
+ 📦
-

Explora el Dashboard

-

- Revisa las métricas principales y el estado de tu inventario +

Proveedores e Inventario

+

+ Tracking automático de stock

- -
-
- 2 + +
+
+ 👨‍🍳
-

Registra Ventas Diarias

-

- Mantén tus datos actualizados para mejores predicciones +

Recetas

+

+ Costos automáticos por producto

- -
-
- 3 + +
+
+ ✅
-

Configura Alertas

-

- Recibe notificaciones sobre inventario bajo y productos próximos a vencer +

Estándares de Calidad

+

+ Monitoreo de producción +

+
+
+ +
+
+ 👥 +
+
+

Equipo

+

+ Coordinación y tareas

- {/* Action Button */} -
+ {/* Action Buttons */} +
+
diff --git a/frontend/src/components/domain/setup-wizard/SetupWizard.tsx b/frontend/src/components/domain/setup-wizard/SetupWizard.tsx new file mode 100644 index 00000000..090748ae --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/SetupWizard.tsx @@ -0,0 +1,367 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { useAuth } from '../../../contexts/AuthContext'; +import { useUserProgress, useMarkStepCompleted } from '../../../api/hooks/onboarding'; +import { StepProgress } from './components/StepProgress'; +import { StepNavigation } from './components/StepNavigation'; +import { Card, CardHeader, CardBody } from '../../ui/Card'; +import { + WelcomeStep, + SuppliersSetupStep, + InventorySetupStep, + RecipesSetupStep, + QualitySetupStep, + TeamSetupStep, + CompletionStep +} from './steps'; + +// Step weights for weighted progress calculation +const STEP_WEIGHTS = { + 'setup-welcome': 5, // 2 min (light) + 'suppliers-setup': 10, // 5 min (moderate) + 'inventory-items-setup': 20, // 10 min (heavy) + 'recipes-setup': 20, // 10 min (heavy) + 'quality-setup': 15, // 7 min (moderate) + 'team-setup': 10, // 5 min (optional) + 'setup-completion': 5 // 2 min (light) +}; + +export interface SetupStepConfig { + id: string; + title: string; + description: string; + component: React.ComponentType; + minRequired?: number; // Minimum items to proceed + isOptional?: boolean; // Can be skipped + estimatedMinutes?: number; // For UI display + weight: number; // For progress calculation +} + +export interface SetupStepProps { + onNext: () => void; + onPrevious: () => void; + onComplete: (data?: any) => void; + onSkip?: () => void; + isFirstStep: boolean; + isLastStep: boolean; + canContinue?: boolean; +} + +export const SetupWizard: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { user } = useAuth(); + + // Define setup wizard steps (Steps 5-11 in overall onboarding) + const SETUP_STEPS: SetupStepConfig[] = [ + { + id: 'setup-welcome', + title: t('setup_wizard:steps.welcome.title', 'Welcome & Setup Overview'), + description: t('setup_wizard:steps.welcome.description', 'Let\'s set up your bakery operations'), + component: WelcomeStep, + isOptional: true, + estimatedMinutes: 2, + weight: STEP_WEIGHTS['setup-welcome'] + }, + { + id: 'suppliers-setup', + title: t('setup_wizard:steps.suppliers.title', 'Add Suppliers'), + description: t('setup_wizard:steps.suppliers.description', 'Your ingredient and material providers'), + component: SuppliersSetupStep, + minRequired: 1, + isOptional: false, + estimatedMinutes: 5, + weight: STEP_WEIGHTS['suppliers-setup'] + }, + { + id: 'inventory-items-setup', + title: t('setup_wizard:steps.inventory.title', 'Set Up Inventory Items'), + description: t('setup_wizard:steps.inventory.description', 'Ingredients and materials you use'), + component: InventorySetupStep, + minRequired: 3, + isOptional: false, + estimatedMinutes: 10, + weight: STEP_WEIGHTS['inventory-items-setup'] + }, + { + id: 'recipes-setup', + title: t('setup_wizard:steps.recipes.title', 'Create Recipes'), + description: t('setup_wizard:steps.recipes.description', 'Your bakery\'s production formulas'), + component: RecipesSetupStep, + minRequired: 1, + isOptional: false, + estimatedMinutes: 10, + weight: STEP_WEIGHTS['recipes-setup'] + }, + { + id: 'quality-setup', + title: t('setup_wizard:steps.quality.title', 'Define Quality Standards'), + description: t('setup_wizard:steps.quality.description', 'Standards for consistent production'), + component: QualitySetupStep, + minRequired: 2, + isOptional: true, + estimatedMinutes: 7, + weight: STEP_WEIGHTS['quality-setup'] + }, + { + id: 'team-setup', + title: t('setup_wizard:steps.team.title', 'Add Team Members'), + description: t('setup_wizard:steps.team.description', 'Your bakery staff'), + component: TeamSetupStep, + minRequired: 0, + isOptional: true, + estimatedMinutes: 5, + weight: STEP_WEIGHTS['team-setup'] + }, + { + id: 'setup-completion', + title: t('setup_wizard:steps.completion.title', 'You\'re All Set!'), + description: t('setup_wizard:steps.completion.description', 'Your bakery system is ready'), + component: CompletionStep, + isOptional: false, + estimatedMinutes: 2, + weight: STEP_WEIGHTS['setup-completion'] + } + ]; + + // State management + const [currentStepIndex, setCurrentStepIndex] = useState(0); + const [isInitialized, setIsInitialized] = useState(false); + const [canContinue, setCanContinue] = useState(false); + + // Get user progress from backend + const { data: userProgress, isLoading: isLoadingProgress } = useUserProgress( + user?.id || '', + { enabled: !!user?.id } + ); + + const markStepCompleted = useMarkStepCompleted(); + + // Calculate weighted progress percentage + const calculateProgress = (): number => { + if (!userProgress) return 0; + + const totalWeight = Object.values(STEP_WEIGHTS).reduce((a, b) => a + b); + let completedWeight = 0; + + // Add weight of fully completed steps + SETUP_STEPS.forEach((step, index) => { + if (index < currentStepIndex) { + const stepProgress = userProgress.steps.find(s => s.step_name === step.id); + if (stepProgress?.completed) { + completedWeight += step.weight; + } + } + }); + + // Add 50% of current step weight (user is midway through) + const currentStep = SETUP_STEPS[currentStepIndex]; + completedWeight += currentStep.weight * 0.5; + + return Math.round((completedWeight / totalWeight) * 100); + }; + + const progressPercentage = calculateProgress(); + + // Initialize step index based on backend progress + useEffect(() => { + if (userProgress && !isInitialized) { + console.log('🔄 Initializing setup wizard progress:', userProgress); + + // Find first incomplete step + let stepIndex = 0; + for (let i = 0; i < SETUP_STEPS.length; i++) { + const step = SETUP_STEPS[i]; + const stepProgress = userProgress.steps.find(s => s.step_name === step.id); + + if (!stepProgress?.completed && stepProgress?.status !== 'skipped') { + stepIndex = i; + console.log(`📍 Resuming at step: "${step.id}" (index ${i})`); + break; + } + } + + // If all steps complete, go to last step + if (stepIndex === 0 && SETUP_STEPS.every(step => { + const stepProgress = userProgress.steps.find(s => s.step_name === step.id); + return stepProgress?.completed || stepProgress?.status === 'skipped'; + })) { + stepIndex = SETUP_STEPS.length - 1; + console.log('✅ All steps completed, going to completion step'); + } + + setCurrentStepIndex(stepIndex); + setIsInitialized(true); + } + }, [userProgress, isInitialized]); + + const currentStep = SETUP_STEPS[currentStepIndex]; + + // Navigation handlers + const handleNext = () => { + if (currentStepIndex < SETUP_STEPS.length - 1) { + setCurrentStepIndex(currentStepIndex + 1); + setCanContinue(false); // Reset for next step + } + }; + + const handlePrevious = () => { + if (currentStepIndex > 0) { + setCurrentStepIndex(currentStepIndex - 1); + } + }; + + const handleSkip = async () => { + if (!user?.id || !currentStep.isOptional) return; + + console.log(`⏭️ Skipping step: "${currentStep.id}"`); + + try { + // Mark step as skipped (not completed) + await markStepCompleted.mutateAsync({ + userId: user.id, + stepName: currentStep.id, + data: { + skipped: true, + skipped_at: new Date().toISOString() + } + }); + + console.log(`✅ Step "${currentStep.id}" marked as skipped`); + + // Move to next step + handleNext(); + } catch (error) { + console.error(`❌ Error skipping step "${currentStep.id}":`, error); + } + }; + + const handleStepComplete = async (data?: any) => { + if (!user?.id) { + console.error('User ID not available'); + return; + } + + // Prevent concurrent mutations + if (markStepCompleted.isPending) { + console.warn(`⚠️ Step completion already in progress for "${currentStep.id}"`); + return; + } + + console.log(`🎯 Completing step: "${currentStep.id}" with data:`, data); + + try { + // Mark step as completed in backend + await markStepCompleted.mutateAsync({ + userId: user.id, + stepName: currentStep.id, + data: { + ...data, + completed_at: new Date().toISOString() + } + }); + + console.log(`✅ Successfully completed step: "${currentStep.id}"`); + + // Handle completion step navigation + if (currentStep.id === 'setup-completion') { + console.log('🎉 Setup wizard completed! Navigating to dashboard...'); + navigate('/app/dashboard'); + } else { + // Auto-advance to next step + handleNext(); + } + } catch (error: any) { + console.error(`❌ Error completing step "${currentStep.id}":`, error); + + const errorMessage = error?.response?.data?.detail || error?.message || 'Unknown error'; + alert(`${t('setup_wizard:errors.step_failed', 'Error completing step')} "${currentStep.title}": ${errorMessage}`); + } + }; + + // Show loading state while initializing + if (isLoadingProgress || !isInitialized) { + return ( +
+ + +
+
+

+ {t('common:loading', 'Loading your setup progress...')} +

+
+
+
+
+ ); + } + + const StepComponent = currentStep.component; + + return ( +
+ {/* Progress Header */} + + + {/* Step Content */} + + +
+
+
+ {currentStepIndex + 1} +
+
+
+

+ {currentStep.title} +

+

+ {currentStep.description} +

+
+ {currentStep.estimatedMinutes && ( +
+ ⏱️ ~{currentStep.estimatedMinutes} min +
+ )} +
+
+ + + + + + {/* Navigation Footer */} +
+ +
+
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/components/StepNavigation.tsx b/frontend/src/components/domain/setup-wizard/components/StepNavigation.tsx new file mode 100644 index 00000000..adc8c931 --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/components/StepNavigation.tsx @@ -0,0 +1,140 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button } from '../../../ui/Button'; +import type { SetupStepConfig } from '../SetupWizard'; + +interface StepNavigationProps { + currentStep: SetupStepConfig; + currentStepIndex: number; + totalSteps: number; + canContinue: boolean; + onPrevious: () => void; + onNext: () => void; + onSkip: () => void; + onComplete: (data?: any) => void; + isLoading: boolean; +} + +export const StepNavigation: React.FC = ({ + currentStep, + currentStepIndex, + totalSteps, + canContinue, + onPrevious, + onNext, + onSkip, + onComplete, + isLoading +}) => { + const { t } = useTranslation(); + + const isFirstStep = currentStepIndex === 0; + const isLastStep = currentStepIndex === totalSteps - 1; + const canSkip = currentStep.isOptional; + + // Determine button text based on step + const getNextButtonText = () => { + if (isLastStep) { + return t('setup_wizard:navigation.go_to_dashboard', 'Go to Dashboard →'); + } + + // Get next step name + const nextStepIndex = currentStepIndex + 1; + if (nextStepIndex < totalSteps) { + const nextStep = currentStep; // Will be dynamically determined + return t('setup_wizard:navigation.continue_to', 'Continue →'); + } + + return t('setup_wizard:navigation.continue', 'Continue →'); + }; + + const handleSkipClick = () => { + // Show confirmation dialog for non-trivial skips + if (currentStep.minRequired && currentStep.minRequired > 0) { + const confirmed = window.confirm( + t('setup_wizard:confirm_skip', + 'Are you sure you want to skip {{stepName}}? You can set this up later from Settings.', + { stepName: currentStep.title } + ) + ); + if (!confirmed) return; + } + + onSkip(); + }; + + return ( +
+ {/* Left side - Back button */} +
+ {!isFirstStep && ( + + )} +
+ + {/* Right side - Skip and Continue buttons */} +
+ {canSkip && !isLastStep && ( + + )} + + +
+ + {/* Helper text */} + {!canContinue && !currentStep.isOptional && !isLastStep && ( +
+ {currentStep.minRequired && currentStep.minRequired > 0 ? ( + t('setup_wizard:min_required', 'Add at least {{count}} {{itemType}} to continue', { + count: currentStep.minRequired, + itemType: getItemType(currentStep.id) + }) + ) : ( + t('setup_wizard:complete_to_continue', 'Complete this step to continue') + )} +
+ )} +
+ ); +}; + +// Helper function to get readable item type from step ID +function getItemType(stepId: string): string { + const types: Record = { + 'suppliers-setup': 'supplier', + 'inventory-items-setup': 'inventory item', + 'recipes-setup': 'recipe', + 'quality-setup': 'quality check', + 'team-setup': 'team member' + }; + + return types[stepId] || 'item'; +} diff --git a/frontend/src/components/domain/setup-wizard/components/StepProgress.tsx b/frontend/src/components/domain/setup-wizard/components/StepProgress.tsx new file mode 100644 index 00000000..1105384c --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/components/StepProgress.tsx @@ -0,0 +1,186 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Card } from '../../../ui/Card'; +import type { SetupStepConfig } from '../SetupWizard'; + +interface StepProgressProps { + steps: SetupStepConfig[]; + currentStepIndex: number; + progressPercentage: number; + userProgress: any; // UserProgress from backend +} + +export const StepProgress: React.FC = ({ + steps, + currentStepIndex, + progressPercentage, + userProgress +}) => { + const { t } = useTranslation(); + + return ( + + {/* Header */} +
+
+

+ {t('setup_wizard:title', 'Set Up Your Bakery Operations')} +

+

+ {t('setup_wizard:subtitle', 'Complete setup to unlock all features')} +

+
+
+
+ {t('setup_wizard:progress.step_of', 'Step {{current}} of {{total}}', { + current: currentStepIndex + 1, + total: steps.length + })} +
+
+ {progressPercentage}% {t('setup_wizard:progress.completed', 'complete')} +
+
+
+ + {/* Progress Bar */} +
+
+
+ + {/* Mobile Step Indicators - Horizontal scroll on small screens */} +
+
+ {steps.map((step, index) => { + const stepProgress = userProgress?.steps.find((s: any) => s.step_name === step.id); + const isCompleted = stepProgress?.completed || index < currentStepIndex; + const isCurrent = index === currentStepIndex; + const isSkipped = stepProgress?.status === 'skipped'; + + return ( +
+
+ {isCompleted ? ( +
+ + + +
+ ) : isCurrent ? ( +
+ {index + 1} +
+ ) : isSkipped ? ( +
+ Skip +
+ ) : ( +
+ {index + 1} +
+ )} +
+
+ {step.title} +
+ {step.isOptional && ( +
+ {t('setup_wizard:optional', 'optional')} +
+ )} +
+ ); + })} +
+
+ + {/* Desktop Step Indicators */} +
+ {steps.map((step, index) => { + const stepProgress = userProgress?.steps.find((s: any) => s.step_name === step.id); + const isCompleted = stepProgress?.completed || index < currentStepIndex; + const isCurrent = index === currentStepIndex; + const isSkipped = stepProgress?.status === 'skipped'; + + return ( +
+
+ {isCompleted ? ( +
+ + + +
+ ) : isCurrent ? ( +
+ {index + 1} +
+ ) : isSkipped ? ( +
+ {t('setup_wizard:skipped', 'Skip')} +
+ ) : ( +
+ {index + 1} +
+ )} +
+
+ {step.title} +
+
+ {step.description} +
+ {step.isOptional && ( +
+ {t('setup_wizard:optional', 'optional')} +
+ )} +
+ ); + })} +
+ + ); +}; diff --git a/frontend/src/components/domain/setup-wizard/components/index.ts b/frontend/src/components/domain/setup-wizard/components/index.ts new file mode 100644 index 00000000..644e9ea4 --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/components/index.ts @@ -0,0 +1,2 @@ +export { StepProgress } from './StepProgress'; +export { StepNavigation } from './StepNavigation'; diff --git a/frontend/src/components/domain/setup-wizard/index.ts b/frontend/src/components/domain/setup-wizard/index.ts new file mode 100644 index 00000000..37cdb60d --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/index.ts @@ -0,0 +1,4 @@ +export { SetupWizard } from './SetupWizard'; +export type { SetupStepConfig, SetupStepProps } from './SetupWizard'; +export * from './steps'; +export * from './components'; diff --git a/frontend/src/components/domain/setup-wizard/steps/CompletionStep.tsx b/frontend/src/components/domain/setup-wizard/steps/CompletionStep.tsx new file mode 100644 index 00000000..8a1279ac --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/steps/CompletionStep.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { Button } from '../../../ui/Button'; +import type { SetupStepProps } from '../SetupWizard'; + +export const CompletionStep: React.FC = ({ onComplete }) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const handleGoToDashboard = () => { + onComplete({ completed: true }); + navigate('/app/dashboard'); + }; + + return ( +
+ {/* Success Icon */} +
+ + + +
+ + {/* Success Message */} +
+

+ {t('setup_wizard:completion.title', 'You\'re All Set! 🎉')} +

+

+ {t('setup_wizard:completion.subtitle', 'Your bakery management system is fully configured and ready to help you run your operations more efficiently.')} +

+
+ + {/* Setup Summary */} +
+

+ {t('setup_wizard:completion.summary_title', 'Setup Summary')} +

+
+
+ + + + {t('setup_wizard:completion.summary_suppliers', 'Suppliers configured')} +
+
+ + + + {t('setup_wizard:completion.summary_inventory', 'Inventory items added')} +
+
+ + + + {t('setup_wizard:completion.summary_recipes', 'Recipes created')} +
+
+ + + + {t('setup_wizard:completion.summary_quality', 'Quality standards defined')} +
+
+
+ + {/* What You Can Do Now */} +
+

+ {t('setup_wizard:completion.what_now_title', 'What You Can Do Now')} +

+
+
+
+ 📦 +
+

+ {t('setup_wizard:completion.feature_inventory', 'Track Inventory')} +

+

+ {t('setup_wizard:completion.feature_inventory_desc', 'Real-time stock levels & alerts')} +

+
+ +
+
+ 👨‍🍳 +
+

+ {t('setup_wizard:completion.feature_production', 'Create Production Orders')} +

+

+ {t('setup_wizard:completion.feature_production_desc', 'Plan daily baking with your recipes')} +

+
+ +
+
+ 💰 +
+

+ {t('setup_wizard:completion.feature_costs', 'Analyze Costs')} +

+

+ {t('setup_wizard:completion.feature_costs_desc', 'See exact costs per recipe')} +

+
+ +
+
+ 📈 +
+

+ {t('setup_wizard:completion.feature_forecasts', 'View AI Forecasts')} +

+

+ {t('setup_wizard:completion.feature_forecasts_desc', 'Demand predictions for your products')} +

+
+
+
+ + {/* Action Button */} +
+ +
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/steps/InventorySetupStep.tsx b/frontend/src/components/domain/setup-wizard/steps/InventorySetupStep.tsx new file mode 100644 index 00000000..3ed57d73 --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/steps/InventorySetupStep.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from '../SetupWizard'; + +export const InventorySetupStep: React.FC = () => { + const { t } = useTranslation(); + + return ( +
+
+

+ + + + {t('setup_wizard:why_this_matters', 'Why This Matters')} +

+

+ {t('setup_wizard:inventory.why', 'Inventory items are the building blocks of your recipes. Once set up, the system will track quantities, alert you when stock is low, and help you calculate recipe costs.')} +

+
+ +
+
+ 📦 +
+

+ {t('setup_wizard:inventory.placeholder_title', 'Inventory Management')} +

+

+ {t('setup_wizard:inventory.placeholder_desc', 'This feature will be implemented in Phase 2')} +

+

+ {t('setup_wizard:inventory.min_required', 'Minimum required: 3 inventory items')} +

+
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx b/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx new file mode 100644 index 00000000..428da360 --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from '../SetupWizard'; + +export const QualitySetupStep: React.FC = () => { + const { t } = useTranslation(); + + return ( +
+
+

+ + + + {t('setup_wizard:why_this_matters', 'Why This Matters')} +

+

+ {t('setup_wizard:quality.why', 'Quality checks ensure consistent output and help you identify issues early. Define what "good" looks like for each stage of production.')} +

+
+ +
+
+ ✅ +
+

+ {t('setup_wizard:quality.placeholder_title', 'Quality Standards')} +

+

+ {t('setup_wizard:quality.placeholder_desc', 'This feature will be implemented in Phase 3')} +

+

+ {t('setup_wizard:quality.min_required', 'Minimum required: 2 quality checks')} +

+
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/steps/RecipesSetupStep.tsx b/frontend/src/components/domain/setup-wizard/steps/RecipesSetupStep.tsx new file mode 100644 index 00000000..e7e320b4 --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/steps/RecipesSetupStep.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from '../SetupWizard'; + +export const RecipesSetupStep: React.FC = () => { + const { t } = useTranslation(); + + return ( +
+
+

+ + + + {t('setup_wizard:why_this_matters', 'Why This Matters')} +

+

+ {t('setup_wizard:recipes.why', 'Recipes connect your inventory to production. The system will calculate exact costs per item, track ingredient consumption, and help you optimize your menu profitability.')} +

+
+ +
+
+ 👨‍🍳 +
+

+ {t('setup_wizard:recipes.placeholder_title', 'Recipes Management')} +

+

+ {t('setup_wizard:recipes.placeholder_desc', 'This feature will be implemented in Phase 2')} +

+

+ {t('setup_wizard:recipes.min_required', 'Minimum required: 1 recipe')} +

+
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/steps/SuppliersSetupStep.tsx b/frontend/src/components/domain/setup-wizard/steps/SuppliersSetupStep.tsx new file mode 100644 index 00000000..e8f6a7a7 --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/steps/SuppliersSetupStep.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from '../SetupWizard'; + +export const SuppliersSetupStep: React.FC = () => { + const { t } = useTranslation(); + + return ( +
+ {/* Why This Matters */} +
+

+ + + + {t('setup_wizard:why_this_matters', 'Why This Matters')} +

+

+ {t('setup_wizard:suppliers.why', 'Suppliers are the source of your ingredients. Setting them up now lets you track costs, manage orders, and analyze supplier performance.')} +

+
+ + {/* Placeholder content - will be implemented in Phase 2 */} +
+
+ + + +
+

+ {t('setup_wizard:suppliers.placeholder_title', 'Suppliers Management')} +

+

+ {t('setup_wizard:suppliers.placeholder_desc', 'This feature will be implemented in Phase 2')} +

+

+ {t('setup_wizard:suppliers.min_required', 'Minimum required: 1 supplier')} +

+
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/steps/TeamSetupStep.tsx b/frontend/src/components/domain/setup-wizard/steps/TeamSetupStep.tsx new file mode 100644 index 00000000..a4400ae2 --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/steps/TeamSetupStep.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from '../SetupWizard'; + +export const TeamSetupStep: React.FC = () => { + const { t } = useTranslation(); + + return ( +
+
+

+ + + + {t('setup_wizard:why_this_matters', 'Why This Matters')} +

+

+ {t('setup_wizard:team.why', 'Adding team members allows you to assign tasks, track who does what, and give everyone the tools they need to work efficiently.')} +

+
+ +
+
+ 👥 +
+

+ {t('setup_wizard:team.placeholder_title', 'Team Management')} +

+

+ {t('setup_wizard:team.placeholder_desc', 'This feature will be implemented in Phase 3')} +

+

+ {t('setup_wizard:team.optional', 'This step is optional')} +

+
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/steps/WelcomeStep.tsx b/frontend/src/components/domain/setup-wizard/steps/WelcomeStep.tsx new file mode 100644 index 00000000..a2306d28 --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/steps/WelcomeStep.tsx @@ -0,0 +1,146 @@ +import React, { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button } from '../../../ui/Button'; +import type { SetupStepProps } from '../SetupWizard'; + +export const WelcomeStep: React.FC = ({ onComplete, onSkip }) => { + const { t } = useTranslation(); + + // Automatically enable continue button (this is an info/welcome step) + useEffect(() => { + // This step is always ready to continue + }, []); + + const handleGetStarted = () => { + onComplete({ viewed: true }); + }; + + const handleSkip = () => { + if (onSkip) onSkip(); + }; + + return ( +
+ {/* Welcome Icon */} +
+ + + +
+ + {/* Welcome Message */} +
+

+ {t('setup_wizard:welcome.title', 'Excellent! Your AI is Ready')} +

+

+ {t('setup_wizard:welcome.subtitle', 'Now let\'s set up your bakery\'s daily operations so the system can help you manage:')} +

+
+ + {/* Feature Cards */} +
+
+
+ 📦 +
+
+

+ {t('setup_wizard:welcome.feature_inventory', 'Inventory Tracking')} +

+

+ {t('setup_wizard:welcome.feature_inventory_desc', 'Real-time stock levels & reorder alerts')} +

+
+
+ +
+
+ 👨‍🍳 +
+
+

+ {t('setup_wizard:welcome.feature_recipes', 'Recipe Costing')} +

+

+ {t('setup_wizard:welcome.feature_recipes_desc', 'Automatic cost calculation & profitability analysis')} +

+
+
+ +
+
+ ✅ +
+
+

+ {t('setup_wizard:welcome.feature_quality', 'Quality Monitoring')} +

+

+ {t('setup_wizard:welcome.feature_quality_desc', 'Track standards & production quality')} +

+
+
+ +
+
+ 👥 +
+
+

+ {t('setup_wizard:welcome.feature_team', 'Team Coordination')} +

+

+ {t('setup_wizard:welcome.feature_team_desc', 'Assign tasks & track responsibilities')} +

+
+
+
+ + {/* Time Estimate */} +
+
+ + + + + {t('setup_wizard:welcome.time_estimate', 'Takes about 15-20 minutes')} + +
+

+ {t('setup_wizard:welcome.save_resume', 'You can save progress and resume anytime')} +

+
+ + {/* Action Buttons */} +
+ + +
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/steps/index.ts b/frontend/src/components/domain/setup-wizard/steps/index.ts new file mode 100644 index 00000000..c2a69c2c --- /dev/null +++ b/frontend/src/components/domain/setup-wizard/steps/index.ts @@ -0,0 +1,7 @@ +export { WelcomeStep } from './WelcomeStep'; +export { SuppliersSetupStep } from './SuppliersSetupStep'; +export { InventorySetupStep } from './InventorySetupStep'; +export { RecipesSetupStep } from './RecipesSetupStep'; +export { QualitySetupStep } from './QualitySetupStep'; +export { TeamSetupStep } from './TeamSetupStep'; +export { CompletionStep } from './CompletionStep'; diff --git a/frontend/src/pages/setup/SetupPage.tsx b/frontend/src/pages/setup/SetupPage.tsx new file mode 100644 index 00000000..a88b7605 --- /dev/null +++ b/frontend/src/pages/setup/SetupPage.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { SetupWizard } from '../../components/domain/setup-wizard'; + +/** + * Setup Page - Wrapper for the Setup Wizard + * This page is accessed after completing the initial onboarding + * and guides users through setting up their bakery operations + * (suppliers, inventory, recipes, quality standards, team) + */ +const SetupPage: React.FC = () => { + return ( +
+ +
+ ); +}; + +export default SetupPage; diff --git a/frontend/src/router/AppRouter.tsx b/frontend/src/router/AppRouter.tsx index f59a9904..788956b4 100644 --- a/frontend/src/router/AppRouter.tsx +++ b/frontend/src/router/AppRouter.tsx @@ -56,8 +56,9 @@ const ModelsConfigPage = React.lazy(() => import('../pages/app/database/models/M const QualityTemplatesPage = React.lazy(() => import('../pages/app/database/quality-templates/QualityTemplatesPage')); const SustainabilityPage = React.lazy(() => import('../pages/app/database/sustainability/SustainabilityPage')); -// Onboarding pages +// Onboarding & Setup pages const OnboardingPage = React.lazy(() => import('../pages/onboarding/OnboardingPage')); +const SetupPage = React.lazy(() => import('../pages/setup/SetupPage')); export const AppRouter: React.FC = () => { return ( @@ -377,13 +378,25 @@ export const AppRouter: React.FC = () => { /> {/* Onboarding Route - Protected but without AppShell */} - - } + } + /> + + {/* Setup Wizard Route - Protected with AppShell */} + + + + + + } /> {/* Default redirects */} diff --git a/frontend/src/router/routes.config.ts b/frontend/src/router/routes.config.ts index c3bd15d7..f85b2089 100644 --- a/frontend/src/router/routes.config.ts +++ b/frontend/src/router/routes.config.ts @@ -163,7 +163,11 @@ export const ROUTES = { HELP_TUTORIALS: '/help/tutorials', HELP_SUPPORT: '/help/support', HELP_FEEDBACK: '/help/feedback', - + + // Onboarding & Setup + ONBOARDING: '/app/onboarding', + SETUP: '/app/setup', + // Error pages NOT_FOUND: '/404', UNAUTHORIZED: '/401', @@ -558,7 +562,24 @@ export const routesConfig: RouteConfig[] = [ }, }, - + // Setup Wizard - Bakery operations setup (post-onboarding) + { + path: '/app/setup', + name: 'Setup', + component: 'SetupPage', + title: 'Configurar Operaciones', + description: 'Configure suppliers, inventory, recipes, and quality standards', + icon: 'settings', + requiresAuth: true, + showInNavigation: false, + meta: { + hideHeader: false, // Show header for easy navigation + hideSidebar: false, // Show sidebar for context + fullScreen: false, + }, + }, + + // Error pages { path: ROUTES.NOT_FOUND,