Improve the UI add button

This commit is contained in:
Urtzi Alfaro
2025-11-16 22:13:52 +01:00
parent 54b7a5e080
commit d36f2ab9af
23 changed files with 2047 additions and 1740 deletions

View File

@@ -1,10 +1,11 @@
import React, { useState, useCallback, useMemo } from 'react';
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
import { Sparkles } from 'lucide-react';
import { WizardModal, WizardStep } from '../../ui/WizardModal/WizardModal';
import { ItemTypeSelector, ItemType } from './ItemTypeSelector';
import { AnyWizardData } from './types';
// Import specific wizards
import { InventoryWizardSteps } from './wizards/InventoryWizard';
import { InventoryWizardSteps, ProductTypeStep, BasicInfoStep, StockConfigStep } from './wizards/InventoryWizard';
import { SupplierWizardSteps } from './wizards/SupplierWizard';
import { RecipeWizardSteps } from './wizards/RecipeWizard';
import { EquipmentWizardSteps } from './wizards/EquipmentWizard';
@@ -31,12 +32,22 @@ export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
const [selectedItemType, setSelectedItemType] = useState<ItemType | null>(
initialItemType || null
);
const [wizardData, setWizardData] = useState<Record<string, any>>({});
const [wizardData, setWizardData] = useState<AnyWizardData>({});
// Use a ref to store the current data - this allows step components
// to always access the latest data without causing the steps array to be recreated
const dataRef = useRef<AnyWizardData>({});
// Update ref whenever data changes
useEffect(() => {
dataRef.current = wizardData;
}, [wizardData]);
// Reset state when modal closes
const handleClose = useCallback(() => {
setSelectedItemType(initialItemType || null);
setWizardData({});
dataRef.current = {};
onClose();
}, [onClose, initialItemType]);
@@ -45,11 +56,23 @@ export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
setSelectedItemType(itemType);
}, []);
// CRITICAL FIX: Update both ref AND state, but wizardSteps won't recreate
// The step component needs to re-render to show typed text (controlled inputs)
// But wizardSteps useMemo ensures steps array doesn't recreate, so no component recreation
const handleDataChange = useCallback((newData: AnyWizardData) => {
// Update ref first for immediate access
dataRef.current = newData;
// Update state to trigger re-render (controlled inputs need this)
setWizardData(newData);
}, []);
// Handle wizard completion
const handleWizardComplete = useCallback(
(data?: any) => {
if (selectedItemType) {
onComplete?.(selectedItemType, data);
// On completion, sync the ref to state for submission
setWizardData(dataRef.current);
onComplete?.(selectedItemType, dataRef.current);
}
handleClose();
},
@@ -57,10 +80,10 @@ export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
);
// Get wizard steps based on selected item type
// CRITICAL: Memoize the steps to prevent component recreation on every render
// Without this, every keystroke causes the component to unmount/remount, losing focus
// IMPORTANT: For dynamic wizards (like sales-entry), we need to include the entryMethod
// in the dependency array so steps update when the user selects manual vs upload
// ARCHITECTURAL SOLUTION: We pass dataRef and setWizardData to wizard step functions.
// The wizard steps use these in their component wrappers, which creates a closure
// that always accesses the CURRENT data from dataRef.current, without needing
// to recreate the steps array on every data change.
const wizardSteps = useMemo((): WizardStep[] => {
if (!selectedItemType) {
// Step 0: Item Type Selection
@@ -76,30 +99,31 @@ export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
];
}
// Return specific wizard steps based on selected type
// Pass dataRef and setWizardData - the wizard step functions will use
// dataRef.current to always access fresh data without recreating steps
switch (selectedItemType) {
case 'inventory':
return InventoryWizardSteps(wizardData, setWizardData);
return InventoryWizardSteps(dataRef, setWizardData);
case 'supplier':
return SupplierWizardSteps(wizardData, setWizardData);
return SupplierWizardSteps(dataRef, setWizardData);
case 'recipe':
return RecipeWizardSteps(wizardData, setWizardData);
return RecipeWizardSteps(dataRef, setWizardData);
case 'equipment':
return EquipmentWizardSteps(wizardData, setWizardData);
return EquipmentWizardSteps(dataRef, setWizardData);
case 'quality-template':
return QualityTemplateWizardSteps(wizardData, setWizardData);
return QualityTemplateWizardSteps(dataRef, setWizardData);
case 'customer-order':
return CustomerOrderWizardSteps(wizardData, setWizardData);
return CustomerOrderWizardSteps(dataRef, setWizardData);
case 'customer':
return CustomerWizardSteps(wizardData, setWizardData);
return CustomerWizardSteps(dataRef, setWizardData);
case 'team-member':
return TeamMemberWizardSteps(wizardData, setWizardData);
return TeamMemberWizardSteps(dataRef, setWizardData);
case 'sales-entry':
return SalesEntryWizardSteps(wizardData, setWizardData);
return SalesEntryWizardSteps(dataRef, setWizardData);
default:
return [];
}
}, [selectedItemType, handleItemTypeSelect, wizardData.entryMethod]); // Include only critical fields for dynamic step generation
}, [selectedItemType, handleItemTypeSelect, wizardData.entryMethod]); // Add entryMethod for dynamic sales-entry steps
// Get wizard title based on selected item type
const getWizardTitle = (): string => {
@@ -131,6 +155,8 @@ export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
steps={wizardSteps}
icon={<Sparkles className="w-6 h-6" />}
size="xl"
dataRef={dataRef}
onDataChange={handleDataChange}
/>
);
};