From f07e527502d958c3228c3e1c85d21996cfd1bded Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Nov 2025 10:12:01 +0000 Subject: [PATCH] fix: CRITICAL - Fix auto-generation interfering with text input CRITICAL BUG FIX: The auto-generation useEffect hooks were watching the name/input fields in their dependency arrays, causing them to fire on EVERY KEYSTROKE. This created state updates while users were typing, causing inputs to lose focus and become unresponsive. ROOT CAUSE: - InventoryWizard: useEffect([inventoryData.name, inventoryData.sku]) - QualityTemplateWizard: useEffect([templateData.name, templateData.templateCode]) - CustomerOrderWizard: useEffect([orderData.orderNumber]) Every keystroke in the name field triggered the useEffect, which called both setLocalState() and onDataChange(), causing double re-renders that interfered with typing. SOLUTION: 1. Added useRef to track if we've already generated the code/SKU/number 2. Changed dependency arrays to ONLY watch the generated field, not the input field 3. Only generate once when name has 3+ characters and generated field is empty 4. Allow regeneration if user manually clears the generated field IMPACT: - Users can now type continuously without interruption - Auto-generation still works after 3 characters are typed - Manual editing of generated fields is preserved - No more UI freezing or losing focus while typing FILES CHANGED: - InventoryWizard.tsx: Fixed SKU auto-generation - QualityTemplateWizard.tsx: Fixed template code auto-generation - CustomerOrderWizard.tsx: Fixed order number auto-generation --- .../wizards/CustomerOrderWizard.tsx | 18 +++++++++++++++--- .../wizards/InventoryWizard.tsx | 19 ++++++++++++++++--- .../wizards/QualityTemplateWizard.tsx | 19 ++++++++++++++++--- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/domain/unified-wizard/wizards/CustomerOrderWizard.tsx b/frontend/src/components/domain/unified-wizard/wizards/CustomerOrderWizard.tsx index 7e15dd14..a1ab0c7b 100644 --- a/frontend/src/components/domain/unified-wizard/wizards/CustomerOrderWizard.tsx +++ b/frontend/src/components/domain/unified-wizard/wizards/CustomerOrderWizard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal'; import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection'; import Tooltip from '../../../ui/Tooltip/Tooltip'; @@ -526,6 +526,8 @@ const OrderItemsStep: React.FC = ({ data, onDataChange }) => { // Step 3: Delivery & Payment with ALL fields const DeliveryPaymentStep: React.FC = ({ data, onDataChange }) => { + const hasGeneratedOrderNumRef = useRef(false); + const [orderData, setOrderData] = useState({ // Required fields requestedDeliveryDate: data.requestedDeliveryDate || '', @@ -603,14 +605,24 @@ const DeliveryPaymentStep: React.FC = ({ data, onDataChange }) }); // Auto-generate order number if not provided + // Only watches orderNumber field to avoid unnecessary re-renders useEffect(() => { - if (!orderData.orderNumber) { + // Only auto-generate order number if: + // 1. We haven't generated before + // 2. Order number is empty + if (!hasGeneratedOrderNumRef.current && !orderData.orderNumber) { + hasGeneratedOrderNumRef.current = true; const orderNum = `ORD-${Date.now().toString().slice(-8)}`; const newData = { ...orderData, orderNumber: orderNum }; setOrderData(newData); onDataChange({ ...data, ...newData }); } - }, [orderData.orderNumber]); + + // If user manually clears the order number, allow regeneration + if (hasGeneratedOrderNumRef.current && !orderData.orderNumber) { + hasGeneratedOrderNumRef.current = false; + } + }, [orderData.orderNumber]); // Only watch orderNumber - prevents unnecessary interference // Update parent whenever order data changes const handleOrderDataChange = (newOrderData: any) => { diff --git a/frontend/src/components/domain/unified-wizard/wizards/InventoryWizard.tsx b/frontend/src/components/domain/unified-wizard/wizards/InventoryWizard.tsx index b980adbb..1a37382a 100644 --- a/frontend/src/components/domain/unified-wizard/wizards/InventoryWizard.tsx +++ b/frontend/src/components/domain/unified-wizard/wizards/InventoryWizard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal'; import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection'; import Tooltip from '../../../ui/Tooltip/Tooltip'; @@ -11,6 +11,8 @@ interface WizardDataProps extends WizardStepProps { // Single comprehensive step with all fields const InventoryDetailsStep: React.FC = ({ data, onDataChange }) => { + const hasGeneratedSkuRef = useRef(false); + const [inventoryData, setInventoryData] = useState({ // Required fields name: data.name || '', @@ -79,14 +81,25 @@ const InventoryDetailsStep: React.FC = ({ data, onDataChange }) }); // Auto-generate SKU from name if not provided + // Only watches SKU field to avoid interfering with typing in name field useEffect(() => { - if (!inventoryData.sku && inventoryData.name) { + // Only auto-generate SKU if: + // 1. We haven't generated before + // 2. SKU is empty + // 3. Name has at least 3 characters (so we can take substring) + if (!hasGeneratedSkuRef.current && !inventoryData.sku && inventoryData.name && inventoryData.name.length >= 3) { + hasGeneratedSkuRef.current = true; const sku = `SKU-${inventoryData.name.substring(0, 3).toUpperCase()}-${Date.now().toString().slice(-4)}`; const newData = { ...inventoryData, sku }; setInventoryData(newData); onDataChange({ ...data, ...newData }); } - }, [inventoryData.name, inventoryData.sku]); + + // If user manually clears the SKU, allow regeneration + if (hasGeneratedSkuRef.current && !inventoryData.sku) { + hasGeneratedSkuRef.current = false; + } + }, [inventoryData.sku]); // Only watch SKU, not name - prevents interference with typing // Update parent whenever local state changes const handleDataChange = (newInventoryData: any) => { diff --git a/frontend/src/components/domain/unified-wizard/wizards/QualityTemplateWizard.tsx b/frontend/src/components/domain/unified-wizard/wizards/QualityTemplateWizard.tsx index 4e750d7f..40743c00 100644 --- a/frontend/src/components/domain/unified-wizard/wizards/QualityTemplateWizard.tsx +++ b/frontend/src/components/domain/unified-wizard/wizards/QualityTemplateWizard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal'; import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection'; import Tooltip from '../../../ui/Tooltip/Tooltip'; @@ -11,6 +11,8 @@ interface WizardDataProps extends WizardStepProps { // Single comprehensive step with all fields const QualityTemplateDetailsStep: React.FC = ({ data, onDataChange }) => { + const hasGeneratedCodeRef = useRef(false); + const [templateData, setTemplateData] = useState({ // Required fields name: data.name || '', @@ -51,14 +53,25 @@ const QualityTemplateDetailsStep: React.FC = ({ data, onDataCha }); // Auto-generate template code from name if not provided + // Only watches templateCode field to avoid interfering with typing in name field useEffect(() => { - if (!templateData.templateCode && templateData.name) { + // Only auto-generate template code if: + // 1. We haven't generated before + // 2. Template code is empty + // 3. Name has at least 3 characters (so we can take substring) + if (!hasGeneratedCodeRef.current && !templateData.templateCode && templateData.name && templateData.name.length >= 3) { + hasGeneratedCodeRef.current = true; const code = `TPL-${templateData.name.substring(0, 3).toUpperCase()}-${Date.now().toString().slice(-4)}`; const newData = { ...templateData, templateCode: code }; setTemplateData(newData); onDataChange({ ...data, ...newData }); } - }, [templateData.name, templateData.templateCode]); + + // If user manually clears the template code, allow regeneration + if (hasGeneratedCodeRef.current && !templateData.templateCode) { + hasGeneratedCodeRef.current = false; + } + }, [templateData.templateCode]); // Only watch templateCode, not name - prevents interference with typing // Update parent whenever local state changes const handleDataChange = (newTemplateData: any) => {