Implement Phase 3: Advanced post-onboarding features (JTBD-driven UX)
Complete JTBD implementation with 4 advanced features to reduce friction and accelerate configuration for non-technical bakery owners. **1. Recipe Templates Library:** - Add RecipeTemplateSelector modal with searchable template gallery - Pre-built templates: Baguette, Pan de Molde, Medialunas, Facturas, Bizcochuelo, Galletas - Smart ingredient matching between templates and user's inventory - Category filtering (Panes, Facturas, Tortas, Galletitas) - One-click template loading with pre-filled wizard data - "Create from scratch" option for custom recipes - Integrated as pre-wizard step in RecipeWizardModal **2. Bulk Supplier CSV Import:** - Add BulkSupplierImportModal with CSV upload & parsing - Downloadable CSV template with examples - Live validation with error detection - Preview table showing valid/invalid rows - Multi-column support (15+ fields: name, type, email, phone, payment terms, address, etc.) - Batch import with progress tracking - Success/error notifications **3. Configuration Recovery (Auto-save):** - Add useWizardDraft hook with localStorage persistence - Auto-save wizard progress every 30 seconds - 7-day draft expiration (configurable TTL) - DraftRecoveryPrompt component for restore/discard choice - Shows "saved X time ago" with human-friendly formatting - Prevents data loss from accidental browser closes **4. Milestone Notifications (Feature Unlocks):** - Add Toast notification system (ToastNotification, ToastContainer, useToast hook) - Support for success, error, info, and milestone toast types - Animated slide-in/slide-out transitions - Auto-dismiss with configurable duration - useFeatureUnlocks hook to track when features are unlocked - Visual feedback for configuration milestones **Benefits:** - Templates: Reduce recipe creation time from 10+ min to <2 min - Bulk Import: Add 50+ suppliers in seconds vs hours - Auto-save: Zero data loss from accidental exits - Notifications: Clear feedback on progress and unlocked capabilities Files: - RecipeTemplateSelector: Template library UI - BulkSupplierImportModal: CSV import system - useWizardDraft + DraftRecoveryPrompt: Auto-save infrastructure - Toast system + useToast + useFeatureUnlocks: Notification framework Part of 3-phase JTBD implementation (Phase 1: Progress Widget, Phase 2: Wizards, Phase 3: Advanced Features).
This commit is contained in:
@@ -5,6 +5,7 @@ import { RecipeProductStep } from './RecipeProductStep';
|
||||
import { RecipeIngredientsStep } from './RecipeIngredientsStep';
|
||||
import { RecipeProductionStep } from './RecipeProductionStep';
|
||||
import { RecipeReviewStep } from './RecipeReviewStep';
|
||||
import { RecipeTemplateSelector } from './RecipeTemplateSelector';
|
||||
import type { RecipeCreate, RecipeIngredientCreate, MeasurementUnit } from '../../../../api/types/recipes';
|
||||
import { useIngredients } from '../../../../api/hooks/inventory';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
@@ -20,6 +21,10 @@ export const RecipeWizardModal: React.FC<RecipeWizardModalProps> = ({
|
||||
onClose,
|
||||
onCreateRecipe
|
||||
}) => {
|
||||
// Template selector state
|
||||
const [showTemplateSelector, setShowTemplateSelector] = useState(true);
|
||||
const [wizardStarted, setWizardStarted] = useState(false);
|
||||
|
||||
// Recipe state
|
||||
const [recipeData, setRecipeData] = useState<Partial<RecipeCreate>>({
|
||||
difficulty_level: 1,
|
||||
@@ -118,6 +123,46 @@ export const RecipeWizardModal: React.FC<RecipeWizardModalProps> = ({
|
||||
setRecipeData(data);
|
||||
};
|
||||
|
||||
const handleSelectTemplate = (templateData: Partial<RecipeCreate>) => {
|
||||
setRecipeData({
|
||||
...recipeData,
|
||||
...templateData
|
||||
});
|
||||
setShowTemplateSelector(false);
|
||||
setWizardStarted(true);
|
||||
};
|
||||
|
||||
const handleStartFromScratch = () => {
|
||||
setShowTemplateSelector(false);
|
||||
setWizardStarted(true);
|
||||
};
|
||||
|
||||
const handleCloseWizard = () => {
|
||||
setShowTemplateSelector(true);
|
||||
setWizardStarted(false);
|
||||
setRecipeData({
|
||||
difficulty_level: 1,
|
||||
yield_quantity: 1,
|
||||
yield_unit: 'units' as MeasurementUnit,
|
||||
serves_count: 1,
|
||||
prep_time_minutes: 0,
|
||||
cook_time_minutes: 0,
|
||||
rest_time_minutes: 0,
|
||||
target_margin_percentage: 30,
|
||||
batch_size_multiplier: 1.0,
|
||||
is_seasonal: false,
|
||||
is_signature_item: false,
|
||||
ingredients: [{
|
||||
ingredient_id: '',
|
||||
quantity: 1,
|
||||
unit: 'grams' as MeasurementUnit,
|
||||
ingredient_order: 1,
|
||||
is_optional: false
|
||||
}]
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleComplete = async () => {
|
||||
try {
|
||||
// Generate recipe code if not provided
|
||||
@@ -182,26 +227,7 @@ export const RecipeWizardModal: React.FC<RecipeWizardModalProps> = ({
|
||||
await onCreateRecipe(finalRecipeData);
|
||||
|
||||
// Reset state
|
||||
setRecipeData({
|
||||
difficulty_level: 1,
|
||||
yield_quantity: 1,
|
||||
yield_unit: 'units' as MeasurementUnit,
|
||||
serves_count: 1,
|
||||
prep_time_minutes: 0,
|
||||
cook_time_minutes: 0,
|
||||
rest_time_minutes: 0,
|
||||
target_margin_percentage: 30,
|
||||
batch_size_multiplier: 1.0,
|
||||
is_seasonal: false,
|
||||
is_signature_item: false,
|
||||
ingredients: [{
|
||||
ingredient_id: '',
|
||||
quantity: 1,
|
||||
unit: 'grams' as MeasurementUnit,
|
||||
ingredient_order: 1,
|
||||
is_optional: false
|
||||
}]
|
||||
});
|
||||
handleCloseWizard();
|
||||
} catch (error) {
|
||||
console.error('Error creating recipe:', error);
|
||||
throw error;
|
||||
@@ -284,14 +310,26 @@ export const RecipeWizardModal: React.FC<RecipeWizardModalProps> = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<WizardModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onComplete={handleComplete}
|
||||
title="Nueva Receta"
|
||||
steps={steps}
|
||||
icon={<ChefHat className="w-6 h-6" />}
|
||||
size="2xl"
|
||||
/>
|
||||
<>
|
||||
{/* Template Selector */}
|
||||
<RecipeTemplateSelector
|
||||
isOpen={isOpen && showTemplateSelector && !wizardStarted}
|
||||
onClose={handleCloseWizard}
|
||||
onSelectTemplate={handleSelectTemplate}
|
||||
onStartFromScratch={handleStartFromScratch}
|
||||
availableIngredients={availableIngredients.map(ing => ({ id: ing.value, name: ing.label }))}
|
||||
/>
|
||||
|
||||
{/* Wizard Modal */}
|
||||
<WizardModal
|
||||
isOpen={isOpen && !showTemplateSelector && wizardStarted}
|
||||
onClose={handleCloseWizard}
|
||||
onComplete={handleComplete}
|
||||
title="Nueva Receta"
|
||||
steps={steps}
|
||||
icon={<ChefHat className="w-6 h-6" />}
|
||||
size="2xl"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user