Improve onboarding flow

This commit is contained in:
Urtzi Alfaro
2026-01-04 21:37:44 +01:00
parent 47ccea4900
commit 429e724a2c
13 changed files with 1052 additions and 213 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
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';
@@ -6,8 +6,11 @@ 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;
@@ -55,6 +58,49 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
}));
});
// 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;
@@ -138,7 +184,7 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
if (stockEntriesToSync.length > 0) {
// Create or update stock entries
// Note: useAddStock currently handles creation/initial set.
// 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 =>
@@ -156,6 +202,19 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
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);
@@ -255,7 +314,7 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
{hasStock && <CheckCircle className="w-4 h-4 text-[var(--color-success)]" />}
</div>
{product.category && (
<div className="text-xs text-[var(--text-secondary)]">{product.category}</div>
<div className="text-xs text-[var(--text-secondary)] uppercase">{t(`inventory:enums.ingredient_category.${product.category}`, product.category)}</div>
)}
</div>
<div className="flex items-center gap-2">
@@ -269,7 +328,7 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
className="w-20 sm:w-24 text-right min-h-[44px]"
/>
<span className="text-sm text-[var(--text-secondary)] whitespace-nowrap">
{product.unit || 'kg'}
{t(`inventory:enums.unit_of_measure.${product.unit}`, product.unit || 'kg')}
</span>
</div>
</div>
@@ -306,7 +365,7 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
{hasStock && <CheckCircle className="w-4 h-4 text-[var(--color-info)]" />}
</div>
{product.category && (
<div className="text-xs text-[var(--text-secondary)]">{product.category}</div>
<div className="text-xs text-[var(--text-secondary)] uppercase">{t(`inventory:enums.ingredient_category.${product.category}`, product.category)}</div>
)}
</div>
<div className="flex items-center gap-2">
@@ -320,7 +379,7 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
className="w-24 text-right"
/>
<span className="text-sm text-[var(--text-secondary)] whitespace-nowrap">
{product.unit || t('common:units', 'unidades')}
{t(`inventory:enums.unit_of_measure.${product.unit}`, product.unit || t('common:units', 'unidades'))}
</span>
</div>
</div>