import React, { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Package, Salad, AlertCircle, ArrowRight, ArrowLeft, CheckCircle } from 'lucide-react'; import Button from '../../../ui/Button/Button'; import Card from '../../../ui/Card/Card'; import Input from '../../../ui/Input/Input'; import { useCurrentTenant } from '../../../../stores/tenant.store'; import { useAddStock, useStock } from '../../../../api/hooks/inventory'; import { useAutoSaveDraft, useStepDraft, useDeleteStepDraft } from '../../../../api/hooks/onboarding'; import InfoCard from '../../../ui/InfoCard'; const STEP_NAME = 'initial-stock-entry'; export interface ProductWithStock { id: string; name: string; type: 'ingredient' | 'finished_product'; category?: string; unit?: string; initialStock?: number; } export interface InitialStockEntryStepProps { products?: ProductWithStock[]; // Made optional - will use empty array if not provided onUpdate?: (data: { productsWithStock: ProductWithStock[] }) => void; onComplete?: () => void; onPrevious?: () => void; initialData?: { productsWithStock?: ProductWithStock[]; }; } export const InitialStockEntryStep: React.FC = ({ products: initialProducts, onUpdate, onComplete, onPrevious, initialData, }) => { const { t } = useTranslation(); const currentTenant = useCurrentTenant(); const tenantId = currentTenant?.id || ''; const addStockMutation = useAddStock(); const { data: stockData } = useStock(tenantId); const [isSaving, setIsSaving] = useState(false); const [products, setProducts] = useState(() => { if (initialData?.productsWithStock) { return initialData.productsWithStock; } // Handle case where initialProducts is undefined (shouldn't happen, but defensive) if (!initialProducts || initialProducts.length === 0) { return []; } return initialProducts.map(p => ({ ...p, initialStock: p.initialStock ?? undefined, })); }); // Draft auto-save hooks const { data: draftData, isLoading: isLoadingDraft } = useStepDraft(STEP_NAME); const { saveDraft, cancelPendingSave } = useAutoSaveDraft(STEP_NAME, 2000); const deleteStepDraft = useDeleteStepDraft(); const initializedRef = useRef(false); const draftCheckedRef = useRef(false); const stepCompletedRef = useRef(false); // Track if step has been completed to prevent draft saves after completion // Restore draft data on mount (only once) useEffect(() => { if (isLoadingDraft || initializedRef.current) return; initializedRef.current = true; draftCheckedRef.current = true; if (draftData?.draft_data?.products && Array.isArray(draftData.draft_data.products)) { // Restore products with stock values from draft setProducts(draftData.draft_data.products); console.log('✅ Restored initial-stock-entry draft:', draftData.draft_data.products.length, 'products'); } }, [isLoadingDraft, draftData]); // Auto-save draft when products change useEffect(() => { // CRITICAL: Do not save drafts after the step has been completed // This prevents overwriting completed status with draft data if (stepCompletedRef.current) { console.log('⏸️ Skipping draft save - step already completed'); return; } if (!draftCheckedRef.current) return; // Only save if we have products with stock values const productsWithValues = products.filter(p => p.initialStock !== undefined); if (productsWithValues.length === 0) return; const draftPayload = { products, }; saveDraft(draftPayload); }, [products, saveDraft]); // Merge existing stock from backend on mount useEffect(() => { if (!stockData?.items || products.length === 0) return; console.log('🔄 Merging backend stock data into initial stock entry state...', { itemsCount: stockData.items.length }); 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]); // 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'); const handleStockChange = (productId: string, value: string) => { const numValue = value === '' ? undefined : parseFloat(value); const updatedProducts = products.map(p => p.id === productId ? { ...p, initialStock: numValue } : p ); setProducts(updatedProducts); onUpdate?.({ productsWithStock: updatedProducts }); }; const handleSetAllToZero = () => { const updatedProducts = products.map(p => ({ ...p, initialStock: 0 })); setProducts(updatedProducts); onUpdate?.({ productsWithStock: updatedProducts }); }; const handleSkipForNow = () => { // Set all undefined values to 0 const updatedProducts = products.map(p => ({ ...p, initialStock: p.initialStock ?? 0, })); setProducts(updatedProducts); onUpdate?.({ productsWithStock: updatedProducts }); onComplete?.(); }; const handleContinue = async () => { setIsSaving(true); try { // STEP 0: Check for existing stock to avoid duplication const existingStockMap = new Map( stockData?.items?.map(s => [s.ingredient_id, s.current_quantity]) || [] ); // Create stock entries only for products where: // 1. initialStock is defined AND > 0 // 2. AND (it doesn't exist OR the value is different) const stockEntriesToSync = products.filter(p => { const currentVal = p.initialStock ?? 0; const backendVal = existingStockMap.get(p.id); // Only sync if it's new (> 0 and doesn't exist) or changed if (backendVal === undefined) { return currentVal > 0; } return currentVal !== backendVal; }); console.log(`📦 Stock processing: ${stockEntriesToSync.length} to sync, ${products.length - stockEntriesToSync.length} skipped.`); if (stockEntriesToSync.length > 0) { // Create or update stock entries // Note: useAddStock currently handles creation/initial set. // If the backend requires a different endpoint for updates, this might need adjustment. // For onboarding, we assume addStock is a "set-and-forget" for initial levels. const stockPromises = stockEntriesToSync.map(product => addStockMutation.mutateAsync({ tenantId, stockData: { ingredient_id: product.id, current_quantity: product.initialStock || 0, unit_cost: 0, } }) ); await Promise.all(stockPromises); console.log(`✅ Synced ${stockEntriesToSync.length} stock entries successfully`); } // CRITICAL: Mark step as completed BEFORE canceling auto-save // This prevents any pending auto-save from overwriting the completion stepCompletedRef.current = true; // Cancel any pending auto-save and delete draft on successful completion cancelPendingSave(); try { await deleteStepDraft.mutateAsync(STEP_NAME); console.log('✅ Deleted initial-stock-entry draft after successful completion'); } catch { // Ignore errors when deleting draft } onComplete?.(); } catch (error) { console.error('Error syncing stock entries:', error); alert(t('onboarding:stock.error_creating_stock', 'Error al guardar los niveles de stock. Por favor, inténtalo de nuevo.')); } finally { setIsSaving(false); } }; const productsWithStock = products.filter(p => p.initialStock !== undefined && p.initialStock >= 0); const productsWithoutStock = products.filter(p => p.initialStock === undefined); const completionPercentage = products.length > 0 ? (productsWithStock.length / products.length) * 100 : 100; const allCompleted = productsWithoutStock.length === 0; // If no products, show a skip message if (products.length === 0) { return (

{t('onboarding:stock.no_products_title', 'Stock Inicial')}

{t('onboarding:stock.no_products_message', 'Podrás configurar los niveles de stock más tarde en la sección de inventario.')}

); } return (
{/* Why This Matters */} {/* Progress */}
{t('onboarding:stock.progress', 'Progreso de captura')} {productsWithStock.length} / {products.length}
{/* Quick Actions */}
{/* Ingredients Section */} {ingredients.length > 0 && (

{t('onboarding:stock.ingredients', 'Ingredientes')} ({ingredients.length})

{ingredients.map(product => { const hasStock = product.initialStock !== undefined; return (
{product.name} {hasStock && }
{product.category && (
{t(`inventory:enums.ingredient_category.${product.category}`, product.category)}
)}
handleStockChange(product.id, e.target.value)} placeholder="0" min="0" step="0.01" className="w-20 sm:w-24 text-right min-h-[44px]" /> {t(`inventory:enums.unit_of_measure.${product.unit}`, product.unit || 'kg')}
); })}
)} {/* Finished Products Section */} {finishedProducts.length > 0 && (

{t('onboarding:stock.finished_products', 'Productos Terminados')} ({finishedProducts.length})

{finishedProducts.map(product => { const hasStock = product.initialStock !== undefined; return (
{product.name} {hasStock && }
{product.category && (
{t(`inventory:enums.ingredient_category.${product.category}`, product.category)}
)}
handleStockChange(product.id, e.target.value)} placeholder="0" min="0" step="1" className="w-24 text-right" /> {t(`inventory:enums.unit_of_measure.${product.unit}`, product.unit || t('common:units', 'unidades'))}
); })}
)} {/* Warning for incomplete */} {!allCompleted && ( )} {/* Footer Actions */}
); }; export default InitialStockEntryStep;