diff --git a/frontend/src/components/domain/onboarding/steps/CompletionStep.tsx b/frontend/src/components/domain/onboarding/steps/CompletionStep.tsx index 03556a38..db7fd4ce 100644 --- a/frontend/src/components/domain/onboarding/steps/CompletionStep.tsx +++ b/frontend/src/components/domain/onboarding/steps/CompletionStep.tsx @@ -39,249 +39,161 @@ export const CompletionStep: React.FC = ({ }; return ( -
- {/* Confetti effect */} -
-
🎉
-
-
🎊
-
- - {/* Animated Success Icon */} -
-
-
-
- +
+ {/* Success Icon */} +
+
+
+
{/* Success Message */} -
-

- {t('onboarding:completion.congratulations', '¡Felicidades! Tu Sistema Está Listo')} +
+

+ {t('onboarding:completion.congratulations', 'Congratulations! Your System Is Ready')}

-

- {t('onboarding:completion.all_configured', 'Has configurado exitosamente {{name}} con nuestro sistema de gestión inteligente. Todo está listo para empezar a optimizar tu panadería.', { name: currentTenant?.name })} +

+ {t('onboarding:completion.all_configured', 'You have successfully configured {name} with our intelligent management system. Everything is ready to start optimizing your bakery.', { name: currentTenant?.name })}

{/* What You Configured */} -
-

- 📋 - {t('onboarding:completion.what_configured', 'Lo Que Has Configurado')} +
+

+ {t('onboarding:completion.what_configured', 'What You Have Configured')}

-
-
-
- - - -
-
-

{t('onboarding:completion.bakery_info', 'Información de Panadería')}

-

{t('onboarding:completion.bakery_info_desc', 'Datos básicos registrados')}

-
+
+
+ + {t('onboarding:completion.bakery_info', 'Bakery Information')}
- -
-
- - - -
-
-

{t('onboarding:completion.inventory_ai', 'Inventario con IA')}

-

{t('onboarding:completion.inventory_ai_desc', 'Productos analizados y categorizados')}

-
+
+ + {t('onboarding:completion.inventory_ai', 'AI Inventory')}
- -
-
- - - -
-
-

{t('onboarding:completion.suppliers_added', 'Proveedores Agregados')}

-

{t('onboarding:completion.suppliers_added_desc', 'Red de suministro configurada')}

-
+
+ + {t('onboarding:completion.suppliers_added', 'Suppliers Added')}
- -
-
- - - -
-
-

{t('onboarding:completion.recipes_configured', 'Recetas Configuradas')}

-

{t('onboarding:completion.recipes_configured_desc', 'Producción lista para usar')}

-
+
+ + {t('onboarding:completion.recipes_configured', 'Recipes Configured')}
- -
-
- - - -
-
-

{t('onboarding:completion.quality_set', 'Calidad Establecida')}

-

{t('onboarding:completion.quality_set_desc', 'Estándares definidos')}

-
+
+ + {t('onboarding:completion.quality_set', 'Quality Standards')}
- -
-
- - - -
-
-

{t('onboarding:completion.team_invited', 'Equipo Invitado')}

-

{t('onboarding:completion.team_invited_desc', 'Colaboradores configurados')}

-
-
- -
-
- - - -
-
-

{t('onboarding:completion.ml_model_trained', 'Modelo IA Entrenado')}

-

{t('onboarding:completion.ml_model_trained_desc', 'Predicciones personalizadas activas')}

-
+
+ + {t('onboarding:completion.team_invited', 'Team Members')}
{/* Quick Access Cards */} -
+
{/* Tips for Success */} -
-
-
- +
+
+
+
-

- {t('onboarding:completion.tips_title', 'Consejos para Maximizar tu Éxito')} +

+ {t('onboarding:completion.tips_title', 'Tips to Maximize Your Success')}

-
-
+
    +
  • - - {t('onboarding:completion.tip1', 'Revisa el dashboard diariamente para insights')} - -
-
+ {t('onboarding:completion.tip1', 'Review the dashboard daily for insights')} + +
  • - - {t('onboarding:completion.tip2', 'Actualiza el inventario regularmente')} - -
  • -
    + {t('onboarding:completion.tip2', 'Update inventory regularly')} + +
  • - - {t('onboarding:completion.tip3', 'Usa las predicciones de IA para planificar')} - -
  • -
    + {t('onboarding:completion.tip3', 'Use AI predictions for planning')} + +
  • - - {t('onboarding:completion.tip4', 'Invita a tu equipo para colaborar')} - -
  • -
    + {t('onboarding:completion.tip4', 'Invite your team to collaborate')} + +
    {/* Primary Action Button */} -
    +
    {/* Help Text */}
    - {t('onboarding:completion.need_help', '¿Necesitas ayuda? Visita nuestra')}{' '} + {t('onboarding:completion.need_help', 'Need help? Visit our')}{' '} - {t('onboarding:completion.user_guide', 'guía de usuario')} + {t('onboarding:completion.user_guide', 'user guide')} {' '} - {t('onboarding:completion.or_contact', 'o contacta a nuestro')}{' '} + {t('onboarding:completion.or_contact', 'or contact our')}{' '} - {t('onboarding:completion.support_team', 'equipo de soporte')} + {t('onboarding:completion.support_team', 'support team')}
    diff --git a/frontend/src/components/domain/setup-wizard/data/recipeTemplates.ts b/frontend/src/components/domain/setup-wizard/data/recipeTemplates.ts index e5b4c799..6dbda65f 100644 --- a/frontend/src/components/domain/setup-wizard/data/recipeTemplates.ts +++ b/frontend/src/components/domain/setup-wizard/data/recipeTemplates.ts @@ -25,7 +25,9 @@ export interface RecipeTemplate { totalTime?: number; ingredients: RecipeIngredientTemplate[]; instructions?: string; + instructionsKey?: string; // Translation key for instructions tips?: string[]; + tipsKeys?: string[]; // Translation keys for tips } /** @@ -159,10 +161,15 @@ export const CAKE_RECIPES: RecipeTemplate[] = [ { ingredientName: 'Polvo de Hornear', quantity: 10, unit: MeasurementUnit.GRAMS, alternatives: ['Baking Powder'] }, ], instructions: '1. Beat eggs with sugar until fluffy\n2. Add vanilla\n3. Gently fold in flour and baking powder\n4. Pour into greased pan\n5. Bake at 180°C for 35 minutes', + instructionsKey: 'recipe_templates:bizcochuelo.instructions', tips: [ 'Do not overmix after adding flour', 'Test doneness with toothpick', ], + tipsKeys: [ + 'recipe_templates:bizcochuelo.tip1', + 'recipe_templates:bizcochuelo.tip2', + ], }, ]; diff --git a/frontend/src/components/domain/setup-wizard/steps/CompletionStep.tsx b/frontend/src/components/domain/setup-wizard/steps/CompletionStep.tsx index f539c8df..809af750 100644 --- a/frontend/src/components/domain/setup-wizard/steps/CompletionStep.tsx +++ b/frontend/src/components/domain/setup-wizard/steps/CompletionStep.tsx @@ -51,192 +51,71 @@ export const CompletionStep: React.FC = ({ onComplete, onUpdate }, ]; - const tips = [ - { - icon: '💡', - title: t('setup_wizard:completion.tip1_title', 'Keep Inventory Updated'), - description: t('setup_wizard:completion.tip1_desc', 'Regularly update stock levels to get accurate cost calculations and low-stock alerts'), - }, - { - icon: '📊', - title: t('setup_wizard:completion.tip2_title', 'Monitor Quality Metrics'), - description: t('setup_wizard:completion.tip2_desc', 'Use quality checks during production to identify issues early and maintain consistency'), - }, - { - icon: '🎯', - title: t('setup_wizard:completion.tip3_title', 'Review Analytics Weekly'), - description: t('setup_wizard:completion.tip3_desc', 'Check your production analytics every week to optimize recipes and reduce waste'), - }, - { - icon: '🤝', - title: t('setup_wizard:completion.tip4_title', 'Maintain Supplier Relationships'), - description: t('setup_wizard:completion.tip4_desc', 'Keep supplier information current and track order performance for better partnerships'), - }, - ]; - const handleGoToDashboard = () => { onComplete?.({ completed: true }); navigate('/app/dashboard'); }; return ( -
    - {/* Celebration Header */} -
    -
    - - - -
    -

    - {t('setup_wizard:completion.title', '🎉 Setup Complete!')} +
    + {/* Success Icon */} +
    + + + +
    + + {/* Completion Message */} +
    +

    + {t('setup_wizard:completion.title', 'Setup Complete!')}

    -

    - {t('setup_wizard:completion.subtitle', "Congratulations! Your bakery management system is ready to use. Let's get started with your first tasks.")} +

    + {t('setup_wizard:completion.subtitle', "Your bakery management system is ready to use.")}

    - {/* Confetti Effect Placeholder */} -
    -
    -
    🎊🎉🎊
    -
    -
    - {/* Next Steps */} -
    -

    - - - +
    +

    {t('setup_wizard:completion.next_steps', 'Recommended Next Steps')}

    {nextSteps.map((step, index) => (
    navigate(step.link)} > -
    -
    +
    +
    {step.icon}
    -

    +

    {step.title}

    -

    +

    {step.description}

    -
    ))}
    - {/* Pro Tips */} -
    -

    - - - - {t('setup_wizard:completion.tips', 'Pro Tips for Success')} -

    -
    - {tips.map((tip, index) => ( -
    -
    -
    {tip.icon}
    -
    -

    - {tip.title} -

    -

    - {tip.description} -

    -
    -
    -
    - ))} -
    -
    - - {/* Quick Links */} -
    -

    - - - - {t('setup_wizard:completion.need_help', 'Need Help?')} -

    -
    - - - - - -
    -
    - - {/* Final CTA */} -
    + {/* Action Button */} +
    -

    - {t('setup_wizard:completion.thanks', 'Thank you for completing the setup! Happy baking! 🥖🥐🍰')} -

    ); diff --git a/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx b/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx index ab8d3c36..df9e6cbc 100644 --- a/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx +++ b/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx @@ -1,10 +1,10 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { SetupStepProps } from '../types'; -import { useQualityTemplates, useCreateQualityTemplate } from '../../../../api/hooks/qualityTemplates'; +import { useQualityTemplates, useCreateQualityTemplate, useUpdateQualityTemplate, useDeleteQualityTemplate } from '../../../../api/hooks/qualityTemplates'; import { useCurrentTenant } from '../../../../stores/tenant.store'; import { useAuthUser } from '../../../../stores/auth.store'; -import { QualityCheckType, ProcessStage, QualityCheckTemplate, QualityCheckTemplateCreate } from '../../../../api/types/qualityTemplates'; +import { QualityCheckType, ProcessStage, QualityCheckTemplate } from '../../../../api/types/qualityTemplates'; export const QualitySetupStep: React.FC = ({ onUpdate, onComplete, onPrevious, canContinue }) => { const { t } = useTranslation(); @@ -21,9 +21,12 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet // Mutations const createTemplateMutation = useCreateQualityTemplate(tenantId); + const updateTemplateMutation = useUpdateQualityTemplate(tenantId); + const deleteTemplateMutation = useDeleteQualityTemplate(tenantId); // Form state const [isAdding, setIsAdding] = useState(false); + const [editingId, setEditingId] = useState(null); const [formData, setFormData] = useState({ name: '', check_type: QualityCheckType.VISUAL, @@ -31,6 +34,14 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet applicable_stages: [] as ProcessStage[], is_required: false, is_critical: false, + // Fields for MEASUREMENT, TEMPERATURE, WEIGHT + unit: '', + min_value: '', + max_value: '', + target_value: '', + tolerance_percentage: '', + // Fields for VISUAL + scoring_criteria: {} as Record, }); const [errors, setErrors] = useState>({}); @@ -61,6 +72,36 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet newErrors.stages = t('setup_wizard:quality.errors.stages_required', 'At least one stage is required'); } + // Validate fields for MEASUREMENT, TEMPERATURE, WEIGHT + if ([QualityCheckType.MEASUREMENT, QualityCheckType.TEMPERATURE, QualityCheckType.WEIGHT].includes(formData.check_type)) { + if (!formData.unit.trim()) { + newErrors.unit = t('setup_wizard:quality.errors.unit_required', 'Unit is required for this check type'); + } + + const minVal = parseFloat(formData.min_value); + const maxVal = parseFloat(formData.max_value); + + if (formData.min_value && isNaN(minVal)) { + newErrors.min_value = t('setup_wizard:quality.errors.invalid_number', 'Must be a valid number'); + } + + if (formData.max_value && isNaN(maxVal)) { + newErrors.max_value = t('setup_wizard:quality.errors.invalid_number', 'Must be a valid number'); + } + + if (formData.min_value && formData.max_value && minVal >= maxVal) { + newErrors.max_value = t('setup_wizard:quality.errors.max_greater_than_min', 'Maximum must be greater than minimum'); + } + } + + // Validate tolerance percentage if provided + if (formData.tolerance_percentage) { + const tolerance = parseFloat(formData.tolerance_percentage); + if (isNaN(tolerance) || tolerance < 0 || tolerance > 100) { + newErrors.tolerance_percentage = t('setup_wizard:quality.errors.tolerance_range', 'Must be between 0 and 100'); + } + } + setErrors(newErrors); return Object.keys(newErrors).length === 0; }; @@ -72,7 +113,7 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet if (!validateForm()) return; try { - const templateData: QualityCheckTemplateCreate = { + const templateData: any = { name: formData.name, check_type: (formData.check_type as QualityCheckType) || QualityCheckType.VISUAL, description: formData.description || undefined, @@ -81,10 +122,37 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet is_critical: formData.is_critical, is_active: true, weight: formData.is_critical ? 10 : 5, - created_by: userId || '', }; - await createTemplateMutation.mutateAsync(templateData); + // Only add created_by for new templates + if (!editingId) { + templateData.created_by = userId || ''; + } + + // Add measurement-related fields for MEASUREMENT, TEMPERATURE, WEIGHT + if ([QualityCheckType.MEASUREMENT, QualityCheckType.TEMPERATURE, QualityCheckType.WEIGHT].includes(formData.check_type)) { + templateData.unit = formData.unit; + if (formData.min_value) templateData.min_value = parseFloat(formData.min_value); + if (formData.max_value) templateData.max_value = parseFloat(formData.max_value); + if (formData.target_value) templateData.target_value = parseFloat(formData.target_value); + if (formData.tolerance_percentage) templateData.tolerance_percentage = parseFloat(formData.tolerance_percentage); + } + + // Add scoring_criteria for VISUAL checks + if (formData.check_type === QualityCheckType.VISUAL && Object.keys(formData.scoring_criteria).length > 0) { + templateData.scoring_criteria = formData.scoring_criteria; + } + + if (editingId) { + // Update existing template + await updateTemplateMutation.mutateAsync({ + templateId: editingId, + templateData, + }); + } else { + // Create new template + await createTemplateMutation.mutateAsync(templateData); + } // Reset form resetForm(); @@ -101,16 +169,47 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet applicable_stages: [], is_required: false, is_critical: false, + unit: '', + min_value: '', + max_value: '', + target_value: '', + tolerance_percentage: '', + scoring_criteria: {}, }); setErrors({}); setIsAdding(false); + setEditingId(null); }; - const toggleStage = (stage: ProcessStage) => { - const stages = formData.applicable_stages.includes(stage) - ? formData.applicable_stages.filter((s) => s !== stage) - : [...formData.applicable_stages, stage]; - setFormData({ ...formData, applicable_stages: stages }); + const handleEdit = (template: QualityCheckTemplate) => { + setFormData({ + name: template.name, + check_type: template.check_type, + description: template.description || '', + applicable_stages: template.applicable_stages || [], + is_required: template.is_required, + is_critical: template.is_critical, + unit: template.unit || '', + min_value: template.min_value?.toString() || '', + max_value: template.max_value?.toString() || '', + target_value: template.target_value?.toString() || '', + tolerance_percentage: template.tolerance_percentage?.toString() || '', + scoring_criteria: template.scoring_criteria || {}, + }); + setEditingId(template.id); + setIsAdding(true); + }; + + const handleDelete = async (templateId: string) => { + if (!window.confirm(t('setup_wizard:quality.confirm_delete', 'Are you sure you want to delete this quality check?'))) { + return; + } + + try { + await deleteTemplateMutation.mutateAsync(templateId); + } catch (error) { + console.error('Error deleting quality template:', error); + } }; const checkTypeOptions = [ @@ -218,6 +317,29 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet )}
    +
    + + +

    ))}
    @@ -229,7 +351,10 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet

    - {t('setup_wizard:quality.add_check', 'Add Quality Check')} + {editingId + ? t('setup_wizard:quality.edit_check', 'Edit Quality Check') + : t('setup_wizard:quality.add_check', 'Add Quality Check') + }

    + {/* Conditional fields for MEASUREMENT, TEMPERATURE, WEIGHT */} + {[QualityCheckType.MEASUREMENT, QualityCheckType.TEMPERATURE, QualityCheckType.WEIGHT].includes(formData.check_type) && ( +
    +
    + + + + {t('setup_wizard:quality.measurement_settings', 'Measurement Settings')} +
    + + {/* Unit */} +
    + + setFormData({ ...formData, unit: e.target.value })} + className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.unit ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`} + placeholder={ + formData.check_type === QualityCheckType.TEMPERATURE + ? t('setup_wizard:quality.placeholders.unit_temp', 'e.g., °C, °F') + : formData.check_type === QualityCheckType.WEIGHT + ? t('setup_wizard:quality.placeholders.unit_weight', 'e.g., g, kg, oz, lb') + : t('setup_wizard:quality.placeholders.unit_measurement', 'e.g., cm, mm, inches') + } + /> + {errors.unit &&

    {errors.unit}

    } +
    + + {/* Min and Max Values */} +
    +
    + + setFormData({ ...formData, min_value: e.target.value })} + className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.min_value ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`} + placeholder="0" + /> + {errors.min_value &&

    {errors.min_value}

    } +
    + +
    + + setFormData({ ...formData, max_value: e.target.value })} + className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.max_value ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`} + placeholder="100" + /> + {errors.max_value &&

    {errors.max_value}

    } +
    +
    + + {/* Target Value and Tolerance */} +
    +
    + + setFormData({ ...formData, target_value: e.target.value })} + className="w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]" + placeholder={t('setup_wizard:quality.placeholders.target', 'Optional')} + /> +
    + +
    + + setFormData({ ...formData, tolerance_percentage: e.target.value })} + className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.tolerance_percentage ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`} + placeholder="5" + /> + {errors.tolerance_percentage &&

    {errors.tolerance_percentage}

    } +
    +
    + +

    + {t('setup_wizard:quality.measurement_help', 'Define the acceptable range and target for this measurement.')} +

    +
    + )} + {/* Applicable Stages */}
    {recipes.length >= 1 && ( @@ -495,6 +567,16 @@ export const RecipesSetupStep: React.FC = ({ onUpdate, onComplet

    +