Random import fixes/enhancements
This commit is contained in:
@@ -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}`}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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" />
|
||||
)}
|
||||
@@ -542,4 +573,4 @@ export default React.memo(MultiSelectCell, (prev, next) => {
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
@@ -728,4 +753,4 @@ export const useAiValidation = <T extends string>(
|
||||
revertAiChange,
|
||||
isChangeReverted
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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>({});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user