Added detailed implementation guide documenting: - Complete pattern used across all 6 rewritten wizards - Backend field mappings for remaining wizards (before continuation) - Step-by-step instructions for wizard rewrites - Testing checklists for each wizard type - Type inconsistencies identified and documented - Field mapping reference (camelCase to snake_case) - Best practices for progressive disclosure UX This guide was created during the initial implementation phase and documents the patterns applied to all wizards.
17 KiB
Wizard Improvements - Implementation Guide
Executive Summary
This document provides comprehensive guidance for completing the wizard improvements project based on backend/frontend research and UX best practices.
COMPLETED ✅
- RecipeWizard - Fully rewritten with all 46 backend fields
- CustomerWizard - Fully rewritten with all 31 backend fields
- SupplierWizard - Fully rewritten with all 48 backend fields
- AdvancedOptionsSection - Reusable component created
- Research Documentation - Complete backend/frontend analysis
REMAINING ⏳
- InventoryWizard - 44 backend fields to add
- QualityTemplateWizard - 25 backend fields to add
- CustomerOrderWizard - 72 backend fields to add
- Type Inconsistency Fixes - PaymentTerms enum, field naming
Part 1: What Was Fixed
Critical Issues Resolved
1. RecipeWizard.tsx:505 Error
Problem: TypeError: a.map is not a function
Root Cause:
// Line 387 - BEFORE
const result = await qualityTemplateService.getTemplates(...);
setTemplates(result); // ❌ result = {templates: [], total: 0, ...}
// Line 505
{templates.map((template) => ( // ❌ templates is object, not array
Solution:
// Line 387 - AFTER
const result = await qualityTemplateService.getTemplates(...);
setTemplates(result.templates || []); // ✅ Extract array
2. Duplicate Next Buttons
Problem: Two "Next" buttons causing UX confusion
- WizardModal footer button (no validation)
- Step component button (with validation)
Solution:
- Removed all internal step buttons
- Used WizardModal's
validateprop:
{
id: 'recipe-details',
validate: () => !!(data.name && data.finishedProductId && data.yieldQuantity),
component: (props) => <RecipeDetailsStep {...props} />
}
3. Missing Required Backend Fields
Problem: Wizards missing fields that backend requires
Examples Fixed:
- Recipe:
version,difficulty_level,status(with proper defaults) - Customer:
customer_code(with auto-generation) - Supplier:
supplier_type,status,payment_terms,currency,standard_lead_time
4. No Advanced Options
Problem: All fields shown at once = overwhelming forms
Solution: Progressive disclosure with AdvancedOptionsSection
<AdvancedOptionsSection
title="Advanced Options"
description="Optional fields for detailed management"
>
{/* 20-30 optional fields here */}
</AdvancedOptionsSection>
Part 2: Implementation Pattern
All three completed wizards follow this exact pattern:
File Structure
import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection';
import Tooltip from '../../../ui/Tooltip/Tooltip';
interface WizardDataProps extends WizardStepProps {
data: Record<string, any>;
onDataChange: (data: Record<string, any>) => void;
}
const DetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange, onComplete }) => {
const [wizardData, setWizardData] = useState({
// Required fields with defaults
name: data.name || '',
requiredField: data.requiredField || 'default',
// Basic optional fields
email: data.email || '',
// Advanced optional fields (20-40 fields)
advancedField1: data.advancedField1 || '',
advancedField2: data.advancedField2 || '',
// ... more fields
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Auto-generation logic (if applicable)
useEffect(() => {
if (!wizardData.code && wizardData.name) {
const code = `PREFIX-${wizardData.name.substring(0, 3).toUpperCase()}-${Date.now().toString().slice(-4)}`;
setWizardData(prev => ({ ...prev, code }));
}
}, [wizardData.name]);
// Real-time data sync
useEffect(() => {
onDataChange({ ...data, ...wizardData });
}, [wizardData]);
const handleCreate = async () => {
if (!currentTenant?.id) {
setError('Could not obtain tenant information');
return;
}
setLoading(true);
setError(null);
try {
const payload = {
// Map camelCase to snake_case
required_field: wizardData.requiredField,
optional_field: wizardData.optionalField || undefined,
// ...
};
await service.create(currentTenant.id, payload);
showToast.success('Created successfully');
onComplete();
} catch (err: any) {
const errorMessage = err.response?.data?.detail || 'Error creating';
setError(errorMessage);
showToast.error(errorMessage);
} finally {
setLoading(false);
}
};
return (
<div className="space-y-6">
{/* Header */}
{/* Error display */}
{/* Required Fields */}
<div className="space-y-4">
{/* Form fields */}
</div>
{/* Advanced Options */}
<AdvancedOptionsSection>
{/* Optional fields */}
</AdvancedOptionsSection>
{/* Submit Button */}
<div className="flex justify-center pt-4">
<button onClick={handleCreate} disabled={loading}>
{loading ? 'Creating...' : 'Create'}
</button>
</div>
</div>
);
};
export const WizardSteps = (data, setData): WizardStep[] => [
{
id: 'details',
title: 'Details',
description: 'Essential information',
component: (props) => <DetailsStep {...props} data={data} onDataChange={setData} />,
validate: () => !!(data.requiredField1 && data.requiredField2),
},
];
Part 3: Step-by-Step Implementation Guide
For InventoryWizard
Required Backend Fields:
name(String)unit_of_measure(Enum: kg, g, l, ml, units, pcs, pkg, bags, boxes)product_type(Enum: INGREDIENT, FINISHED_PRODUCT - default: INGREDIENT)
Optional Fields to Add in Advanced Section:
- Basic:
sku,barcode,ingredient_category,product_category,description,brand - Pricing:
average_cost,last_purchase_price,standard_cost - Inventory Mgmt:
low_stock_threshold,reorder_point,reorder_quantity,max_stock_level - Product Info:
package_size,shelf_life_days,display_life_hours,best_before_hours - Storage:
storage_instructions,is_perishable - Central Bakery:
central_baker_product_code,delivery_days,minimum_order_quantity,pack_size - Flags:
is_active,produced_locally - References:
recipe_id(for finished products) - Allergens:
allergen_info(JSONB array) - Nutrition:
nutritional_info(JSONB for finished products)
Auto-generation:
useEffect(() => {
if (!wizardData.sku && wizardData.name) {
const sku = `INV-${wizardData.name.substring(0, 3).toUpperCase()}-${Date.now().toString().slice(-4)}`;
setWizardData(prev => ({ ...prev, sku }));
}
}, [wizardData.name]);
For QualityTemplateWizard
Required Backend Fields:
name(String)check_type(String: visual, measurement, temperature, weight, boolean, timing, checklist)weight(Float 0.0-10.0, default: 1.0)created_by(UUID - use currentTenant.id)
Optional Fields to Add in Advanced Section:
- Identification:
template_code - Details:
description,category,instructions - Configuration:
parameters,thresholds,scoring_criteria(all JSONB) - Values:
min_value,max_value,target_value,unit,tolerance_percentage - Flags:
is_active,is_required,is_critical - Stages:
applicable_stages(JSONB array of ProcessStage values)
Note: parameters, thresholds, scoring_criteria are JSONB - consider textarea with JSON validation or structured form builder.
For CustomerOrderWizard
Required Backend Fields:
customer_id(UUID - select from customers)requested_delivery_date(DateTime)order_number(String - auto-generate)status(Enum: pending, confirmed, in_production, ready, out_for_delivery, delivered, cancelled, failed)order_type(Enum: standard, rush, recurring, special - default: standard)priority(Enum: high, normal, low - default: normal)delivery_method(Enum: delivery, pickup - default: delivery)
Optional Fields - MANY (72 total backend fields):
Step 1: Customer & Delivery
delivery_address(JSONB)delivery_instructions,delivery_window_start,delivery_window_endconfirmed_delivery_date,actual_delivery_date
Step 2: Order Items (separate array management)
- OrderItem[] with:
product_id,quantity,unit_price,product_name - Item fields:
customization_details,special_instructions,product_specifications
Step 3: Pricing & Payment (Advanced)
subtotal,discount_amount,discount_percentage,tax_amount,delivery_fee,total_amountpayment_status,payment_method,payment_terms,payment_due_date
Step 4: Additional Info (Advanced)
special_instructions,custom_requirements,allergen_warningsbusiness_model,order_source,sales_channel,order_origin- Production:
production_batch_id,fulfillment_location,estimated_preparation_time - Notifications:
customer_notified_confirmed,customer_notified_ready,customer_notified_delivered - Quality:
quality_score,customer_rating,customer_feedback
Auto-generation:
useEffect(() => {
if (!wizardData.orderNumber) {
const orderNum = `ORD-${new Date().getFullYear()}${(new Date().getMonth() + 1).toString().padStart(2, '0')}${new Date().getDate().toString().padStart(2, '0')}-${Date.now().toString().slice(-6)}`;
setWizardData(prev => ({ ...prev, orderNumber: orderNum }));
}
}, []);
Part 4: Type Inconsistencies to Fix
Issue 1: PaymentTerms Enum Conflict
Problem: Two different enums with same name
Suppliers (frontend/src/api/types/suppliers.ts):
export enum PaymentTerms {
COD = 'cod',
NET_15 = 'net_15',
NET_30 = 'net_30',
NET_45 = 'net_45',
NET_60 = 'net_60',
PREPAID = 'prepaid',
CREDIT_TERMS = 'credit_terms',
}
Orders (frontend/src/api/types/orders.ts):
export enum PaymentTerms {
IMMEDIATE = 'immediate',
NET_30 = 'net_30',
NET_60 = 'net_60',
}
Solution Options:
- Rename one:
SupplierPaymentTermsandCustomerPaymentTerms - Merge into one comprehensive enum (if backend supports)
- Use string literals instead of enum
Recommended Fix:
// frontend/src/api/types/common.ts
export enum SupplierPaymentTerms {
COD = 'cod',
NET_15 = 'net_15',
NET_30 = 'net_30',
NET_45 = 'net_45',
NET_60 = 'net_60',
PREPAID = 'prepaid',
CREDIT_TERMS = 'credit_terms',
}
export enum CustomerPaymentTerms {
IMMEDIATE = 'immediate',
NET_30 = 'net_30',
NET_60 = 'net_60',
}
Then update imports:
// In suppliers wizard
import { SupplierPaymentTerms } from '../../../api/types/common';
// In customers/orders wizard
import { CustomerPaymentTerms } from '../../../api/types/common';
Issue 2: unit_cost vs unit_price
Problem: Inconsistent field naming
Stock Type defines:
unit_cost: number;
Hook uses:
unit_price: number;
Solution: Search and replace all unit_price → unit_cost in inventory hooks/services, OR update backend to accept both.
Files to check:
grep -r "unit_price" frontend/src/api/services/inventory.ts
grep -r "unit_price" frontend/src/api/hooks/useInventory.ts
Part 5: Testing Checklist
For each wizard, verify:
Functional Testing
- All required fields prevent submission when empty
- Validation messages display correctly
- Optional fields don't prevent submission
- Advanced options section expands/collapses
- Auto-generation works (codes, etc.)
- Form submits successfully
- Success toast appears
- Modal closes after success
- Error messages display on failure
- Loading state shows during submission
Field Validation
- Email fields validate format
- Phone fields validate format (if applicable)
- Number fields enforce min/max
- Date fields use proper format
- Enum fields use correct values
- JSONB fields parse correctly
Backend Alignment
- All required backend fields present
- Field names match backend (snake_case)
- Enums match backend values
- Data types match (string, number, boolean)
- Defaults match backend defaults
UX Testing
- Form is not overwhelming (required fields visible, optional hidden)
- Clear visual hierarchy
- Helpful tooltips on complex fields
- Responsive design works on mobile
- Tab order is logical
- Keyboard navigation works
Part 6: Quick Reference
Completed Wizard Examples
Recipe: /frontend/src/components/domain/unified-wizard/wizards/RecipeWizard.tsx
- Best example of complex advanced options
- Shows ingredient list management
- Quality template selection
- Seasonal conditional fields
Customer: /frontend/src/components/domain/unified-wizard/wizards/CustomerWizard.tsx
- Clean single-step wizard
- Auto-code generation
- Address fields in advanced section
Supplier: /frontend/src/components/domain/unified-wizard/wizards/SupplierWizard.tsx
- All payment terms properly aligned
- Certification/specialization handling
- Checkbox fields for preferences
Key Components
AdvancedOptionsSection: /frontend/src/components/ui/AdvancedOptionsSection/AdvancedOptionsSection.tsx
Tooltip: /frontend/src/components/ui/Tooltip/Tooltip.tsx
WizardModal: /frontend/src/components/ui/WizardModal/WizardModal.tsx
Research Documents
Backend Models: /home/user/bakery_ia/FRONTEND_API_TYPES_ANALYSIS.md
API Summary: /home/user/bakery_ia/FRONTEND_API_ANALYSIS_SUMMARY.md
Part 7: Git Workflow
Commits Created
020acc4- Research documentation3b66bb8- RecipeWizard rewrite478d423- CustomerWizard rewriteb596359- SupplierWizard rewrite
Branch
claude/bakery-jtbd-wizard-design-011CUwzatRMmw9L2wVGdXYgm
Part 8: Estimated Effort
Remaining Wizards:
- InventoryWizard: ~2-3 hours (moderate complexity, 44 fields)
- QualityTemplateWizard: ~1-2 hours (simpler, 25 fields, but JSONB handling)
- CustomerOrderWizard: ~4-6 hours (complex, 72 fields, multi-step with items)
Type Fixes:
- PaymentTerms enum: ~30 minutes
- unit_cost vs unit_price: ~15 minutes
Total Remaining: ~8-12 hours
Part 9: Success Criteria
✅ All wizards should:
- Have NO duplicate Next buttons
- Include ALL backend required fields
- Include ALL backend optional fields (in advanced section)
- Use validate prop for field validation
- Auto-generate codes where applicable
- Have English labels
- Use AdvancedOptionsSection component
- Include tooltips for complex fields
- Handle errors gracefully
- Show loading states
✅ All type inconsistencies fixed
✅ All wizards tested end-to-end
Part 10: Future Enhancements (Not in Scope)
- Multi-step wizards for complex entities (e.g., Order with items as separate step)
- Real-time field validation as user types
- Field dependencies (show field X only if field Y has value Z)
- Draft saving (persist wizard state)
- Form analytics (track where users drop off)
- Accessibility improvements (ARIA labels, keyboard shortcuts)
- i18n support (Spanish translations)
Conclusion
This guide provides everything needed to complete the wizard improvements. The pattern is established, components are built, and research is documented. Simply follow the pattern from the completed wizards for each remaining wizard.
Key Principle: Progressive disclosure + complete backend alignment + clean UX = excellent wizard experience.
Appendix: Field Mapping Reference
Recipe → Backend Mapping
// Frontend (camelCase) → Backend (snake_case)
name → name
finishedProductId → finished_product_id
yieldQuantity → yield_quantity
yieldUnit → yield_unit
recipeCode → recipe_code
difficultyLevel → difficulty_level
prepTime → prep_time_minutes
cookTime → cook_time_minutes
restTime → rest_time_minutes
optimalProductionTemp → optimal_production_temperature
optimalHumidity → optimal_humidity
isSeasonal → is_seasonal
isSignatureItem → is_signature_item
seasonStartMonth → season_start_month
seasonEndMonth → season_end_month
targetMargin → target_margin_percentage
Customer → Backend Mapping
name → name
customerCode → customer_code
customerType → customer_type
businessName → business_name
addressLine1 → address_line1
addressLine2 → address_line2
postalCode → postal_code
taxId → tax_id
businessLicense → business_license
paymentTerms → payment_terms
creditLimit → credit_limit
discountPercentage → discount_percentage
customerSegment → customer_segment
priorityLevel → priority_level
preferredDeliveryMethod → preferred_delivery_method
specialInstructions → special_instructions
Supplier → Backend Mapping
name → name
supplierCode → supplier_code
supplierType → supplier_type
taxId → tax_id
registrationNumber → registration_number
contactPerson → contact_person
addressLine1 → address_line1
addressLine2 → address_line2
stateProvince → state_province
postalCode → postal_code
paymentTerms → payment_terms
standardLeadTime → standard_lead_time
creditLimit → credit_limit
minimumOrderAmount → minimum_order_amount
deliveryArea → delivery_area
isPreferredSupplier → is_preferred_supplier
autoApproveEnabled → auto_approve_enabled
Document Version: 1.0 Last Updated: 2025-11-10 Author: Claude (AI Assistant) Status: Reference Implementation Guide