Random import fixes/enhancements

This commit is contained in:
2025-09-06 16:55:35 -04:00
parent 54f55b06a1
commit 5d7d7a8671
6 changed files with 149 additions and 19 deletions

View File

@@ -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 (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
@@ -369,11 +383,11 @@ const CompanySelector = React.memo(({
</PopoverTrigger>
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
<Command>
<CommandInput placeholder="Search companies..." />
<CommandInput placeholder="Search companies..." value={query} onValueChange={setQuery} />
<CommandList className="max-h-[200px] overflow-y-auto" onWheel={handleCommandListWheel}>
<CommandEmpty>No companies found.</CommandEmpty>
<CommandGroup>
{companies?.map((company: any) => (
{filteredCompanies.map((company: any) => (
<CommandItem
key={company.value}
value={`${company.label} ${company.value}`}

View File

@@ -186,9 +186,11 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => {
// Apply global selections to each row of data if they exist
const dataWithGlobalSelections = globalSelections
? dataWithMeta.map((row: Data<string> & { __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;

View File

@@ -961,7 +961,18 @@ const ValidationContainer = <T extends string>({
]);
return (
<div className="flex flex-col h-[calc(100vh-10rem)] overflow-hidden">
<div
className="flex flex-col h-[calc(100vh-10rem)] overflow-hidden"
onMouseUp={() => {
// Prevent stray text selection when clicking away from cells
try {
const sel = window.getSelection?.();
if (sel && sel.type === 'Range') {
sel.removeAllRanges();
}
} catch {}
}}
>
<div className="flex-1 overflow-hidden">
<div className="flex h-[calc(100vh-9.5rem)] flex-col">
<div className="flex-1 overflow-hidden">

View File

@@ -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<T extends string> {
@@ -237,24 +238,43 @@ const MultiSelectCell = <T extends string>({
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 = <T extends string>({
onSelect={() => handleSelect(option.value)}
className="cursor-pointer"
>
{option.label}
<div className="flex items-center gap-2">
{field.key === 'colors' && option.hex && (
<span
className={`inline-block h-3.5 w-3.5 rounded-full ${option.hex.toLowerCase() === '#ffffff' || option.hex.toLowerCase() === '#fff' ? 'border' : ''}`}
style={{
backgroundColor: option.hex,
...(option.hex.toLowerCase() === '#ffffff' || option.hex.toLowerCase() === '#fff' ? { borderColor: '#000' } : {})
}}
/>
)}
<span>{option.label}</span>
</div>
{selectedValueSet.has(option.value) && (
<Check className="ml-auto h-4 w-4" />
)}

View File

@@ -296,10 +296,24 @@ export const useAiValidation = <T extends string>(
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<string, any> = {};
(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 = <T extends string>(
});
}, 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<string, any> = {};
(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);

View File

@@ -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<string, string> = {
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<string, string> = {
"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 = <T extends string>({
initialData,
onBack,
@@ -71,10 +105,23 @@ export const useValidationState = <T extends string>({
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<T>;
});
});
// Row selection state
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});