diff --git a/inventory/src/components/product-import/steps/MatchColumnsStep/MatchColumnsStep.tsx b/inventory/src/components/product-import/steps/MatchColumnsStep/MatchColumnsStep.tsx index e43cbac..1213f6b 100644 --- a/inventory/src/components/product-import/steps/MatchColumnsStep/MatchColumnsStep.tsx +++ b/inventory/src/components/product-import/steps/MatchColumnsStep/MatchColumnsStep.tsx @@ -347,11 +347,25 @@ const CompanySelector = React.memo(({ companies: any[] }) => { const [open, setOpen] = useState(false); + const [query, setQuery] = useState(""); const handleCommandListWheel = (e: React.WheelEvent) => { e.currentTarget.scrollTop += e.deltaY; e.stopPropagation(); }; + // Filtered and sliced list to prevent UI freezes with very large lists + const filteredCompanies = React.useMemo(() => { + if (!query.trim()) { + // When no search, show a capped subset for performance + return (companies || []).slice(0, 200); + } + const q = query.toLowerCase(); + return (companies || []).filter((c: any) => ( + String(c.label || '').toLowerCase().includes(q) || + String(c.value || '').toLowerCase().includes(q) + )); + }, [companies, query]); + return ( @@ -369,11 +383,11 @@ const CompanySelector = React.memo(({ - + No companies found. - {companies?.map((company: any) => ( + {filteredCompanies.map((company: any) => ( { // Apply global selections to each row of data if they exist const dataWithGlobalSelections = globalSelections ? dataWithMeta.map((row: Data & { __index?: string }) => { - const newRow = { ...row }; + const newRow = { ...row } as any; if (globalSelections.supplier) newRow.supplier = globalSelections.supplier; if (globalSelections.company) newRow.company = globalSelections.company; + if (globalSelections.line) newRow.line = globalSelections.line; + if (globalSelections.subline) newRow.subline = globalSelections.subline; return newRow; }) : dataWithMeta; diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/components/ValidationContainer.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/components/ValidationContainer.tsx index 478437e..120e274 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/components/ValidationContainer.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/components/ValidationContainer.tsx @@ -961,7 +961,18 @@ const ValidationContainer = ({ ]); return ( -
+
{ + // Prevent stray text selection when clicking away from cells + try { + const sel = window.getSelection?.(); + if (sel && sel.type === 'Range') { + sel.removeAllRanges(); + } + } catch {} + }} + >
diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx index f7e0503..c83b57b 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx @@ -11,6 +11,7 @@ import { Badge } from '@/components/ui/badge' interface FieldOption { label: string; value: string; + hex?: string; // optional hex color for colors field } interface MultiSelectCellProps { @@ -237,24 +238,43 @@ const MultiSelectCell = ({ if (providedOptions && providedOptions.length > 0) { // Check if options are already in the right format if (typeof providedOptions[0] === 'object' && 'label' in providedOptions[0] && 'value' in providedOptions[0]) { - return providedOptions as FieldOption[]; + // Preserve optional hex if present (hex or hex_color without #) + return (providedOptions as any[]).map(opt => ({ + label: opt.label, + value: String(opt.value), + hex: opt.hex + || (opt.hexColor ? `#${String(opt.hexColor).replace(/^#/, '')}` : undefined) + || (opt.hex_color ? `#${String(opt.hex_color).replace(/^#/, '')}` : undefined) + })) as FieldOption[]; } - return providedOptions.map(option => ({ + return (providedOptions as any[]).map(option => ({ label: option.label || String(option.value), - value: String(option.value) + value: String(option.value), + hex: option.hex + || (option.hexColor ? `#${String(option.hexColor).replace(/^#/, '')}` : undefined) + || (option.hex_color ? `#${String(option.hex_color).replace(/^#/, '')}` : undefined) })); } // Check field options format if (fieldOptions.length > 0) { if (typeof fieldOptions[0] === 'object' && 'label' in fieldOptions[0] && 'value' in fieldOptions[0]) { - return fieldOptions as FieldOption[]; + return (fieldOptions as any[]).map(opt => ({ + label: opt.label, + value: String(opt.value), + hex: opt.hex + || (opt.hexColor ? `#${String(opt.hexColor).replace(/^#/, '')}` : undefined) + || (opt.hex_color ? `#${String(opt.hex_color).replace(/^#/, '')}` : undefined) + })) as FieldOption[]; } - return fieldOptions.map(option => ({ + return (fieldOptions as any[]).map(option => ({ label: option.label || String(option.value), - value: String(option.value) + value: String(option.value), + hex: option.hex + || (option.hexColor ? `#${String(option.hexColor).replace(/^#/, '')}` : undefined) + || (option.hex_color ? `#${String(option.hex_color).replace(/^#/, '')}` : undefined) })); } @@ -491,7 +511,18 @@ const MultiSelectCell = ({ onSelect={() => handleSelect(option.value)} className="cursor-pointer" > - {option.label} +
+ {field.key === 'colors' && option.hex && ( + + )} + {option.label} +
{selectedValueSet.has(option.value) && ( )} @@ -542,4 +573,4 @@ export default React.memo(MultiSelectCell, (prev, next) => { } return true; -}); \ No newline at end of file +}); diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx index 9e40231..0dfc8e3 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx @@ -296,10 +296,24 @@ export const useAiValidation = ( lastProduct: data[data.length - 1] }); - // Clean the data to ensure we only send what's needed + // Build a complete row object including empty cells so API receives all fields const cleanedData = data.map(item => { - const { __index, ...rest } = item; - return rest; + const { __index, ...rest } = item as any; + // Ensure all known field keys are present, even if empty + const withAllKeys: Record = {}; + (fields as any[]).forEach((f) => { + const k = String(f.key); + // Preserve arrays (e.g., multi-select) as empty array if undefined + if (Array.isArray(rest[k])) { + withAllKeys[k] = rest[k]; + } else if (rest[k] === undefined) { + // Use empty string to represent an empty cell + withAllKeys[k] = ""; + } else { + withAllKeys[k] = rest[k]; + } + }); + return withAllKeys; }); console.log('Cleaned data sample:', { @@ -421,10 +435,21 @@ export const useAiValidation = ( }); }, 1000) as unknown as NodeJS.Timeout; - // Clean the data to ensure we only send what's needed + // Build a complete row object including empty cells so API receives all fields const cleanedData = data.map(item => { - const { __index, ...rest } = item; - return rest; + const { __index, ...rest } = item as any; + const withAllKeys: Record = {}; + (fields as any[]).forEach((f) => { + const k = String(f.key); + if (Array.isArray(rest[k])) { + withAllKeys[k] = rest[k]; + } else if (rest[k] === undefined) { + withAllKeys[k] = ""; + } else { + withAllKeys[k] = rest[k]; + } + }); + return withAllKeys; }); console.log('Cleaned data for validation:', cleanedData); @@ -728,4 +753,4 @@ export const useAiValidation = ( revertAiChange, isChangeReverted }; -}; \ No newline at end of file +}; diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useValidationState.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useValidationState.tsx index a503c74..8ae732f 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useValidationState.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useValidationState.tsx @@ -13,6 +13,40 @@ import { useUniqueItemNumbersValidation } from "./useUniqueItemNumbersValidation import { useUpcValidation } from "./useUpcValidation"; import { Props, RowData } from "./validationTypes"; +// Country normalization helper (common mappings) - function declaration for hoisting +function normalizeCountryCode(input: string): string | null { + if (!input) return null; + const s = input.trim(); + const upper = s.toUpperCase(); + if (/^[A-Z]{2}$/.test(upper)) return upper; // already 2-letter + const iso3to2: Record = { + USA: "US", GBR: "GB", UK: "GB", CHN: "CN", DEU: "DE", FRA: "FR", ITA: "IT", ESP: "ES", + CAN: "CA", MEX: "MX", AUS: "AU", NZL: "NZ", JPN: "JP", KOR: "KR", PRK: "KP", TWN: "TW", + VNM: "VN", THA: "TH", IDN: "ID", IND: "IN", BRA: "BR", ARG: "AR", CHL: "CL", PER: "PE", + ZAF: "ZA", RUS: "RU", UKR: "UA", NLD: "NL", BEL: "BE", CHE: "CH", SWE: "SE", NOR: "NO", + DNK: "DK", POL: "PL", AUT: "AT", PRT: "PT", GRC: "GR", CZE: "CZ", HUN: "HU", IRL: "IE", + ISR: "IL", PAK: "PK", BGD: "BD", PHL: "PH", MYS: "MY", SGP: "SG", HKG: "HK", MAC: "MO" + }; + if (iso3to2[upper]) return iso3to2[upper]; + const nameMap: Record = { + "UNITED STATES": "US", "UNITED STATES OF AMERICA": "US", "AMERICA": "US", "U.S.": "US", "U.S.A": "US", "USA": "US", + "UNITED KINGDOM": "GB", "UK": "GB", "GREAT BRITAIN": "GB", "ENGLAND": "GB", + "CHINA": "CN", "PEOPLE'S REPUBLIC OF CHINA": "CN", "PRC": "CN", + "CANADA": "CA", "MEXICO": "MX", "JAPAN": "JP", "SOUTH KOREA": "KR", "KOREA, REPUBLIC OF": "KR", + "TAIWAN": "TW", "VIETNAM": "VN", "THAILAND": "TH", "INDONESIA": "ID", "INDIA": "IN", + "GERMANY": "DE", "FRANCE": "FR", "ITALY": "IT", "SPAIN": "ES", "NETHERLANDS": "NL", "BELGIUM": "BE", + "SWITZERLAND": "CH", "SWEDEN": "SE", "NORWAY": "NO", "DENMARK": "DK", "POLAND": "PL", "AUSTRIA": "AT", + "PORTUGAL": "PT", "GREECE": "GR", "CZECH REPUBLIC": "CZ", "CZECHIA": "CZ", "HUNGARY": "HU", "IRELAND": "IE", + "RUSSIA": "RU", "UKRAINE": "UA", "AUSTRALIA": "AU", "NEW ZEALAND": "NZ", + "BRAZIL": "BR", "ARGENTINA": "AR", "CHILE": "CL", "PERU": "PE", "SOUTH AFRICA": "ZA", + "ISRAEL": "IL", "PAKISTAN": "PK", "BANGLADESH": "BD", "PHILIPPINES": "PH", "MALAYSIA": "MY", "SINGAPORE": "SG", + "HONG KONG": "HK", "MACAU": "MO" + }; + const normalizedName = s.replace(/\./g, "").trim().toUpperCase(); + if (nameMap[normalizedName]) return nameMap[normalizedName]; + return null; +} + export const useValidationState = ({ initialData, onBack, @@ -71,10 +105,23 @@ export const useValidationState = ({ updatedRow.ship_restrictions = "0"; } + // Normalize country code (COO) to 2-letter ISO if possible + if (typeof updatedRow.coo === "string") { + const raw = updatedRow.coo.trim(); + const normalized = normalizeCountryCode(raw); + if (normalized) { + updatedRow.coo = normalized; + } else { + // Uppercase 2-letter values as fallback + if (raw.length === 2) updatedRow.coo = raw.toUpperCase(); + } + } + return updatedRow as RowData; }); }); + // Row selection state const [rowSelection, setRowSelection] = useState({});