Add traslations
This commit is contained in:
@@ -49,7 +49,7 @@ interface StepProps {
|
||||
}
|
||||
|
||||
const OnboardingWizardContent: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
@@ -77,7 +77,8 @@ const OnboardingWizardContent: React.FC = () => {
|
||||
|
||||
// All possible steps with conditional visibility
|
||||
// All step IDs match backend ONBOARDING_STEPS exactly
|
||||
const ALL_STEPS: StepConfig[] = [
|
||||
// Wrapped in useMemo to re-evaluate when language changes
|
||||
const ALL_STEPS: StepConfig[] = useMemo(() => [
|
||||
// Phase 1: Discovery
|
||||
{
|
||||
id: 'bakery-type-selection',
|
||||
@@ -119,8 +120,8 @@ const OnboardingWizardContent: React.FC = () => {
|
||||
// Enterprise-specific: Child Tenants Setup
|
||||
{
|
||||
id: 'child-tenants-setup',
|
||||
title: 'Configurar Sucursales',
|
||||
description: 'Registra las sucursales de tu red empresarial',
|
||||
title: t('onboarding:wizard.steps.child_tenants.title', 'Configurar Sucursales'),
|
||||
description: t('onboarding:wizard.steps.child_tenants.description', 'Registra las sucursales de tu red empresarial'),
|
||||
component: ChildTenantsSetupStep,
|
||||
isConditional: true,
|
||||
condition: () => {
|
||||
@@ -203,7 +204,7 @@ const OnboardingWizardContent: React.FC = () => {
|
||||
description: t('onboarding:steps.completion.description', '¡Todo listo!'),
|
||||
component: CompletionStep,
|
||||
},
|
||||
];
|
||||
], [i18n.language]); // Re-create steps when language changes
|
||||
|
||||
// Filter visible steps based on wizard context
|
||||
// useMemo ensures VISIBLE_STEPS recalculates when wizard context state changes
|
||||
@@ -225,7 +226,7 @@ const OnboardingWizardContent: React.FC = () => {
|
||||
});
|
||||
|
||||
return visibleSteps;
|
||||
}, [wizardContext.state, isEnterprise]); // Added isEnterprise to dependencies
|
||||
}, [ALL_STEPS, wizardContext.state, isEnterprise]); // Added ALL_STEPS to re-filter when translations change
|
||||
|
||||
const isNewTenant = searchParams.get('new') === 'true';
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
||||
|
||||
@@ -58,7 +58,7 @@ export const CompletionStep: React.FC<CompletionStepProps> = ({
|
||||
|
||||
{/* Success Message */}
|
||||
<div className="space-y-5 animate-slide-up">
|
||||
<h1 className="text-4xl md:text-5xl font-bold bg-gradient-to-r from-[var(--color-primary)] via-[var(--color-success)] to-[var(--color-primary)] bg-clip-text text-transparent animate-shimmer" style={{ backgroundSize: '200% auto' }}>
|
||||
<h1 className="text-4xl md:text-5xl font-bold bg-gradient-to-r from-[var(--color-primary)] via-[var(--color-success)] to-[var(--color-primary)] bg-clip-text text-transparent" style={{ backgroundSize: '200% auto' }}>
|
||||
{t('onboarding:completion.congratulations', '¡Felicidades! Tu Sistema Está Listo')}
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl text-[var(--text-secondary)] max-w-3xl mx-auto leading-relaxed">
|
||||
|
||||
@@ -57,27 +57,30 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
|
||||
|
||||
// Merge existing stock from backend on mount
|
||||
useEffect(() => {
|
||||
if (stockData?.items && products.length > 0) {
|
||||
console.log('🔄 Merging backend stock data into initial stock entry state...', { itemsCount: stockData.items.length });
|
||||
if (!stockData?.items || products.length === 0) return;
|
||||
|
||||
let hasChanges = false;
|
||||
const updatedProducts = products.map(p => {
|
||||
const existingStock = stockData?.items?.find(s => s.ingredient_id === p.id);
|
||||
if (existingStock && p.initialStock !== existingStock.current_quantity) {
|
||||
hasChanges = true;
|
||||
return {
|
||||
...p,
|
||||
initialStock: existingStock.current_quantity
|
||||
};
|
||||
}
|
||||
return p;
|
||||
});
|
||||
console.log('🔄 Merging backend stock data into initial stock entry state...', { itemsCount: stockData.items.length });
|
||||
|
||||
if (hasChanges) {
|
||||
setProducts(updatedProducts);
|
||||
let hasChanges = false;
|
||||
const updatedProducts = products.map(p => {
|
||||
const existingStock = stockData.items.find(s => s.ingredient_id === p.id);
|
||||
|
||||
// Only merge if user hasn't entered value yet
|
||||
if (existingStock && p.initialStock === undefined) {
|
||||
hasChanges = true;
|
||||
return {
|
||||
...p,
|
||||
initialStock: existingStock.current_quantity
|
||||
};
|
||||
}
|
||||
return p;
|
||||
});
|
||||
|
||||
if (hasChanges) {
|
||||
setProducts(updatedProducts);
|
||||
onUpdate?.({ productsWithStock: updatedProducts });
|
||||
}
|
||||
}, [stockData, products]); // Run when stock data changes or products list is initialized
|
||||
}, [stockData]); // Only depend on stockData to avoid infinite loop
|
||||
|
||||
const ingredients = products.filter(p => p.type === 'ingredient');
|
||||
const finishedProducts = products.filter(p => p.type === 'finished_product');
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../../ui/Button';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { useCreateIngredient, useIngredients } from '../../../../api/hooks/inventory';
|
||||
import { useCreateIngredient, useBulkCreateIngredients, useIngredients } from '../../../../api/hooks/inventory';
|
||||
import { useImportSalesData } from '../../../../api/hooks/sales';
|
||||
import type { ProductSuggestionResponse, IngredientCreate } from '../../../../api/types/inventory';
|
||||
import { ProductType, UnitOfMeasure, IngredientCategory, ProductCategory } from '../../../../api/types/inventory';
|
||||
@@ -140,6 +140,7 @@ export const InventoryReviewStep: React.FC<InventoryReviewStepProps> = ({
|
||||
|
||||
// API hooks
|
||||
const createIngredientMutation = useCreateIngredient();
|
||||
const bulkCreateIngredientsMutation = useBulkCreateIngredients();
|
||||
const importSalesMutation = useImportSalesData();
|
||||
const { data: existingIngredients } = useIngredients(tenantId);
|
||||
|
||||
@@ -333,26 +334,61 @@ export const InventoryReviewStep: React.FC<InventoryReviewStepProps> = ({
|
||||
|
||||
console.log(`📦 Inventory processing: ${itemsToCreate.length} to create, ${existingMatches.length} already exist.`);
|
||||
|
||||
// STEP 1: Create new inventory items in parallel
|
||||
const createPromises = itemsToCreate.map((item, index) => {
|
||||
const ingredientData: IngredientCreate = {
|
||||
name: item.name,
|
||||
product_type: item.product_type,
|
||||
category: item.category,
|
||||
unit_of_measure: item.unit_of_measure as UnitOfMeasure,
|
||||
};
|
||||
// STEP 1: Create new inventory items using bulk API (with fallback to individual creates)
|
||||
let newlyCreatedIngredients: any[] = [];
|
||||
|
||||
return createIngredientMutation.mutateAsync({
|
||||
tenantId,
|
||||
ingredientData,
|
||||
}).catch(error => {
|
||||
console.error(`❌ Failed to create ingredient "${item.name}":`, error);
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
if (itemsToCreate.length > 0) {
|
||||
try {
|
||||
// Try bulk creation first (more efficient)
|
||||
const ingredientsData: IngredientCreate[] = itemsToCreate.map(item => ({
|
||||
name: item.name,
|
||||
product_type: item.product_type,
|
||||
category: item.category,
|
||||
unit_of_measure: item.unit_of_measure as UnitOfMeasure,
|
||||
}));
|
||||
|
||||
const newlyCreatedIngredients = await Promise.all(createPromises);
|
||||
console.log('✅ New inventory items created successfully');
|
||||
const bulkResult = await bulkCreateIngredientsMutation.mutateAsync({
|
||||
tenantId,
|
||||
ingredients: ingredientsData,
|
||||
});
|
||||
|
||||
// Extract successfully created ingredients
|
||||
newlyCreatedIngredients = bulkResult.results
|
||||
.filter(r => r.success && r.ingredient)
|
||||
.map(r => r.ingredient!);
|
||||
|
||||
console.log(`✅ Bulk creation: ${bulkResult.total_created}/${bulkResult.total_requested} items created successfully`);
|
||||
|
||||
// Log any failures
|
||||
if (bulkResult.total_failed > 0) {
|
||||
const failures = bulkResult.results.filter(r => !r.success);
|
||||
console.warn(`⚠️ ${bulkResult.total_failed} items failed:`, failures.map(f => f.error));
|
||||
}
|
||||
} catch (bulkError) {
|
||||
console.warn('⚠️ Bulk create failed, falling back to individual creates:', bulkError);
|
||||
|
||||
// Fallback: Create items individually in parallel
|
||||
const createPromises = itemsToCreate.map((item, index) => {
|
||||
const ingredientData: IngredientCreate = {
|
||||
name: item.name,
|
||||
product_type: item.product_type,
|
||||
category: item.category,
|
||||
unit_of_measure: item.unit_of_measure as UnitOfMeasure,
|
||||
};
|
||||
|
||||
return createIngredientMutation.mutateAsync({
|
||||
tenantId,
|
||||
ingredientData,
|
||||
}).catch(error => {
|
||||
console.error(`❌ Failed to create ingredient "${item.name}":`, error);
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
|
||||
newlyCreatedIngredients = await Promise.all(createPromises);
|
||||
console.log('✅ Fallback: New inventory items created successfully via individual requests');
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 2: Import sales data (only if file was uploaded)
|
||||
let salesImported = false;
|
||||
@@ -467,10 +503,10 @@ export const InventoryReviewStep: React.FC<InventoryReviewStepProps> = ({
|
||||
<span className="text-3xl group-hover:scale-110 transition-transform">{template.icon}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-medium text-[var(--text-primary)] mb-1">
|
||||
{template.name}
|
||||
{t(`setup_wizard:inventory.templates.${template.id}`, template.name)}
|
||||
</h4>
|
||||
<p className="text-xs text-[var(--text-secondary)] mb-2">
|
||||
{template.description}
|
||||
{t(`setup_wizard:inventory.templates.${template.id}-desc`, template.description)}
|
||||
</p>
|
||||
<p className="text-xs text-purple-600 dark:text-purple-400 font-medium">
|
||||
{template.items.length} {t('inventory:templates.items', 'ingredientes')}
|
||||
|
||||
@@ -592,7 +592,7 @@ export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplet
|
||||
value={formData.yield_quantity}
|
||||
onChange={(e) => setFormData({ ...formData, yield_quantity: e.target.value })}
|
||||
className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.yield_quantity ? '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="10"
|
||||
placeholder={t('setup_wizard:recipes.placeholders.yield_quantity', '10')}
|
||||
/>
|
||||
{errors.yield_quantity && <p className="mt-1 text-xs text-[var(--color-error)]">{errors.yield_quantity}</p>}
|
||||
</div>
|
||||
@@ -666,7 +666,7 @@ export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplet
|
||||
step="0.01"
|
||||
value={ing.quantity}
|
||||
onChange={(e) => updateIngredient(index, 'quantity', e.target.value)}
|
||||
placeholder="Qty"
|
||||
placeholder={t('setup_wizard:recipes.placeholders.ingredient_quantity', 'Qty')}
|
||||
className={`w-full px-2 py-1.5 text-sm bg-[var(--bg-secondary)] border ${errors[`ingredient_${index}_quantity`] ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded focus:outline-none focus:ring-1 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`}
|
||||
/>
|
||||
{errors[`ingredient_${index}_quantity`] && <p className="mt-1 text-xs text-[var(--color-error)]">{errors[`ingredient_${index}_quantity`]}</p>}
|
||||
|
||||
Reference in New Issue
Block a user