Match columns tweaks
This commit is contained in:
@@ -19,13 +19,21 @@ import {
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import config from "@/config"
|
import config from "@/config"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { CheckCircle2, AlertCircle, EyeIcon, EyeOffIcon, ArrowRightIcon, XIcon, FileSpreadsheetIcon, LinkIcon, FileIcon } from "lucide-react"
|
import { CheckCircle2, AlertCircle, EyeIcon, EyeOffIcon, ArrowRightIcon, XIcon, FileSpreadsheetIcon, LinkIcon, FileIcon, CheckIcon, ChevronsUpDown } from "lucide-react"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
export type MatchColumnsProps<T extends string> = {
|
export type MatchColumnsProps<T extends string> = {
|
||||||
data: RawData[]
|
data: RawData[]
|
||||||
@@ -166,15 +174,12 @@ const MemoizedColumnSamplePreview = React.memo(({ samples }: { samples: any[] })
|
|||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent side="right" align="start" className="w-[250px] p-0">
|
<PopoverContent side="right" align="start" className="w-[250px] p-0">
|
||||||
<div className="p-3 border-b">
|
<ScrollArea className="h-[200px] overflow-y-auto">
|
||||||
<h4 className="text-sm font-medium">Sample Data</h4>
|
|
||||||
</div>
|
|
||||||
<ScrollArea className="h-[200px] overflow-auto">
|
|
||||||
<div className="p-3 space-y-2">
|
<div className="p-3 space-y-2">
|
||||||
{samples.map((sample, i) => (
|
{samples.map((sample, i) => (
|
||||||
<div key={i} className="text-sm">
|
<div key={i} className="text-sm">
|
||||||
<span className="text-muted-foreground text-xs mr-2">{i + 1}:</span>
|
|
||||||
<span className="font-medium">{String(sample || '(empty)')}</span>
|
<span className="font-medium">{String(sample || '(empty)')}</span>
|
||||||
|
{i < samples.length - 1 && <Separator className="w-full my-2" />}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -264,6 +269,353 @@ const ValueMappings = memo(({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add these new components before the MatchColumnsStep component
|
||||||
|
const SupplierSelector = React.memo(({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
suppliers
|
||||||
|
}: {
|
||||||
|
value?: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
suppliers: any[]
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const handleCommandListWheel = (e: React.WheelEvent) => {
|
||||||
|
e.currentTarget.scrollTop += e.deltaY;
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
className="w-full justify-between"
|
||||||
|
>
|
||||||
|
{value
|
||||||
|
? suppliers?.find((supplier: any) =>
|
||||||
|
supplier.value === value)?.label || "Select supplier..."
|
||||||
|
: "Select supplier..."}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search suppliers..." />
|
||||||
|
<CommandList className="max-h-[200px] overflow-y-auto" onWheel={handleCommandListWheel}>
|
||||||
|
<CommandEmpty>No suppliers found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{suppliers?.map((supplier: any) => (
|
||||||
|
<CommandItem
|
||||||
|
key={supplier.value}
|
||||||
|
value={supplier.label}
|
||||||
|
onSelect={() => {
|
||||||
|
onChange(supplier.value);
|
||||||
|
setOpen(false); // Close popover after selection
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{supplier.label}
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
value === supplier.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const CompanySelector = React.memo(({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
companies
|
||||||
|
}: {
|
||||||
|
value?: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
companies: any[]
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const handleCommandListWheel = (e: React.WheelEvent) => {
|
||||||
|
e.currentTarget.scrollTop += e.deltaY;
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
className="w-full justify-between"
|
||||||
|
>
|
||||||
|
{value
|
||||||
|
? companies?.find((company: any) =>
|
||||||
|
company.value === value)?.label || "Select company..."
|
||||||
|
: "Select company..."}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search companies..." />
|
||||||
|
<CommandList className="max-h-[200px] overflow-y-auto" onWheel={handleCommandListWheel}>
|
||||||
|
<CommandEmpty>No companies found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{companies?.map((company: any) => (
|
||||||
|
<CommandItem
|
||||||
|
key={company.value}
|
||||||
|
value={company.label}
|
||||||
|
onSelect={() => {
|
||||||
|
onChange(company.value);
|
||||||
|
setOpen(false); // Close popover after selection
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{company.label}
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
value === company.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const LineSelector = React.memo(({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
lines,
|
||||||
|
disabled
|
||||||
|
}: {
|
||||||
|
value?: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
lines: any[];
|
||||||
|
disabled: boolean;
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const handleCommandListWheel = (e: React.WheelEvent) => {
|
||||||
|
e.currentTarget.scrollTop += e.deltaY;
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
className="w-full justify-between"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{value
|
||||||
|
? lines?.find((line: any) =>
|
||||||
|
line.value === value)?.label || "Select line..."
|
||||||
|
: "Select line..."}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search lines..." />
|
||||||
|
<CommandList className="max-h-[200px] overflow-y-auto" onWheel={handleCommandListWheel}>
|
||||||
|
<CommandEmpty>No lines found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{lines?.map((line: any) => (
|
||||||
|
<CommandItem
|
||||||
|
key={line.value}
|
||||||
|
value={line.label}
|
||||||
|
onSelect={() => {
|
||||||
|
onChange(line.value);
|
||||||
|
setOpen(false); // Close popover after selection
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{line.label}
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
value === line.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const SubLineSelector = React.memo(({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
sublines,
|
||||||
|
disabled
|
||||||
|
}: {
|
||||||
|
value?: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
sublines: any[];
|
||||||
|
disabled: boolean;
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const handleCommandListWheel = (e: React.WheelEvent) => {
|
||||||
|
e.currentTarget.scrollTop += e.deltaY;
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
className="w-full justify-between"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{value
|
||||||
|
? sublines?.find((subline: any) =>
|
||||||
|
subline.value === value)?.label || "Select sub line..."
|
||||||
|
: "Select sub line..."}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search sub lines..." />
|
||||||
|
<CommandList className="max-h-[200px] overflow-y-auto" onWheel={handleCommandListWheel}>
|
||||||
|
<CommandEmpty>No sub lines found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{sublines?.map((subline: any) => (
|
||||||
|
<CommandItem
|
||||||
|
key={subline.value}
|
||||||
|
value={subline.label}
|
||||||
|
onSelect={() => {
|
||||||
|
onChange(subline.value);
|
||||||
|
setOpen(false); // Close popover after selection
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{subline.label}
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
value === subline.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add this new component before the MatchColumnsStep component
|
||||||
|
const FieldSelector = React.memo(({
|
||||||
|
column,
|
||||||
|
isUnmapped = false,
|
||||||
|
fieldCategories,
|
||||||
|
allFields,
|
||||||
|
onChange,
|
||||||
|
isFieldMappedToOtherColumn,
|
||||||
|
handleCommandListWheel
|
||||||
|
}: {
|
||||||
|
column: any;
|
||||||
|
isUnmapped?: boolean;
|
||||||
|
fieldCategories: any[];
|
||||||
|
allFields: any[];
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
isFieldMappedToOtherColumn: (fieldKey: string, currentColumnIndex: number) => { isMapped: boolean, columnHeader?: string };
|
||||||
|
handleCommandListWheel: (e: React.WheelEvent) => void;
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
// For ignored columns, show a badge
|
||||||
|
if (column.type === ColumnType.ignored) {
|
||||||
|
return <Badge variant="outline">Ignored</Badge>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current value if this is a mapped column
|
||||||
|
const currentValue = "value" in column ? column.value as string : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
className="w-full justify-between"
|
||||||
|
>
|
||||||
|
{currentValue
|
||||||
|
? allFields.find(f => f.key === currentValue)?.label || "Select field..."
|
||||||
|
: "Select field..."}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search fields..." />
|
||||||
|
<CommandList className="max-h-[300px] overflow-y-auto" onWheel={handleCommandListWheel}>
|
||||||
|
<CommandEmpty>No fields found.</CommandEmpty>
|
||||||
|
{fieldCategories.map(category => (
|
||||||
|
<CommandGroup key={category.name} heading={category.name}>
|
||||||
|
{category.fields.map((field: { key: string; label: string }) => {
|
||||||
|
const { isMapped, columnHeader } = isFieldMappedToOtherColumn(field.key as string, column.index);
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={field.key as string}
|
||||||
|
value={field.key as string}
|
||||||
|
onSelect={(value: string) => {
|
||||||
|
onChange(value);
|
||||||
|
setOpen(false); // Close the popover after selection
|
||||||
|
}}
|
||||||
|
className={isMapped ? "opacity-70" : ""}
|
||||||
|
>
|
||||||
|
<div className="flex-1 flex items-center justify-between">
|
||||||
|
<span>{field.label}</span>
|
||||||
|
{isMapped ? (
|
||||||
|
<span className="text-xs text-muted-foreground ml-2">
|
||||||
|
(mapped to "{columnHeader}")
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{currentValue === field.key && (
|
||||||
|
<CheckIcon className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
))}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const MatchColumnsStep = React.memo(<T extends string>({
|
export const MatchColumnsStep = React.memo(<T extends string>({
|
||||||
data,
|
data,
|
||||||
headerValues,
|
headerValues,
|
||||||
@@ -278,7 +630,7 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
|||||||
([...headerValues] as string[]).map((value, index) => ({ type: ColumnType.empty, index, header: value ?? "" })),
|
([...headerValues] as string[]).map((value, index) => ({ type: ColumnType.empty, index, header: value ?? "" })),
|
||||||
)
|
)
|
||||||
const [globalSelections, setGlobalSelections] = useState<GlobalSelections>(initialGlobalSelections || {})
|
const [globalSelections, setGlobalSelections] = useState<GlobalSelections>(initialGlobalSelections || {})
|
||||||
const [showAllColumns, setShowAllColumns] = useState(false)
|
const [showAllColumns, setShowAllColumns] = useState(true)
|
||||||
const [expandedValueMappings, setExpandedValueMappings] = useState<number[]>([])
|
const [expandedValueMappings, setExpandedValueMappings] = useState<number[]>([])
|
||||||
|
|
||||||
// Use debounce for expensive operations
|
// Use debounce for expensive operations
|
||||||
@@ -601,18 +953,14 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
|||||||
return mappings;
|
return mappings;
|
||||||
}, [columns, fields, isFieldCoveredByGlobalSelections]);
|
}, [columns, fields, isFieldCoveredByGlobalSelections]);
|
||||||
|
|
||||||
// Available fields for mapping (excluding already mapped fields)
|
// Available fields for mapping (including already mapped fields)
|
||||||
const availableFields = useMemo(() => {
|
const availableFields = useMemo(() => {
|
||||||
const fieldsArray = Array.isArray(fields) ? fields : [fields];
|
const fieldsArray = Array.isArray(fields) ? fields : [fields];
|
||||||
const mappedFieldKeys = matchedColumns
|
// Don't filter out mapped fields, only filter global selections
|
||||||
.filter(col => "value" in col)
|
|
||||||
.map(col => (col as any).value);
|
|
||||||
|
|
||||||
return fieldsArray.filter(field =>
|
return fieldsArray.filter(field =>
|
||||||
!mappedFieldKeys.includes(field.key) &&
|
|
||||||
!isFieldCoveredByGlobalSelections(field.key)
|
!isFieldCoveredByGlobalSelections(field.key)
|
||||||
);
|
);
|
||||||
}, [fields, matchedColumns, isFieldCoveredByGlobalSelections]);
|
}, [fields, isFieldCoveredByGlobalSelections]);
|
||||||
|
|
||||||
// All available fields including already mapped ones (for editing mapped columns)
|
// All available fields including already mapped ones (for editing mapped columns)
|
||||||
const allFields = useMemo(() => {
|
const allFields = useMemo(() => {
|
||||||
@@ -963,48 +1311,48 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
|||||||
return handlers;
|
return handlers;
|
||||||
}, [columns, onChange]);
|
}, [columns, onChange]);
|
||||||
|
|
||||||
// Render the field selector for a column
|
// Add a function to check if a field is already mapped to another column
|
||||||
|
const isFieldMappedToOtherColumn = useCallback((fieldKey: string, currentColumnIndex: number) => {
|
||||||
|
const matchedColumnForField = columns.find(col =>
|
||||||
|
col.type !== ColumnType.empty &&
|
||||||
|
col.type !== ColumnType.ignored &&
|
||||||
|
"value" in col &&
|
||||||
|
col.value === fieldKey &&
|
||||||
|
col.index !== currentColumnIndex
|
||||||
|
);
|
||||||
|
return matchedColumnForField ? { isMapped: true, columnHeader: matchedColumnForField.header } : { isMapped: false };
|
||||||
|
}, [columns]);
|
||||||
|
|
||||||
|
// Add a wheel handler function for command lists
|
||||||
|
const handleCommandListWheel = useCallback((e: React.WheelEvent) => {
|
||||||
|
e.currentTarget.scrollTop += e.deltaY;
|
||||||
|
e.stopPropagation();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Replace the renderFieldSelector function with a more stable version
|
||||||
const renderFieldSelector = useCallback((column: Column<T>, isUnmapped: boolean = false) => {
|
const renderFieldSelector = useCallback((column: Column<T>, isUnmapped: boolean = false) => {
|
||||||
// For ignored columns, show a badge
|
// For ignored columns, show a badge
|
||||||
if (column.type === ColumnType.ignored) {
|
if (column.type === ColumnType.ignored) {
|
||||||
return <Badge variant="outline">Ignored</Badge>;
|
return <Badge variant="outline">Ignored</Badge>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current value if this is a mapped column
|
|
||||||
const currentValue = "value" in column ? column.value as string : undefined;
|
|
||||||
|
|
||||||
// Use all fields for mapped columns, and only available fields for unmapped columns
|
|
||||||
const fieldCategoriesForSelector = isUnmapped ? availableFieldCategories : allFieldCategories;
|
|
||||||
|
|
||||||
// Get the pre-created onChange handler for this column
|
// Get the pre-created onChange handler for this column
|
||||||
const handleChange = columnChangeHandlers.get(column.index);
|
const handleChange = columnChangeHandlers.get(column.index);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<FieldSelector
|
||||||
value={currentValue}
|
column={column}
|
||||||
onValueChange={handleChange}
|
isUnmapped={isUnmapped}
|
||||||
>
|
fieldCategories={availableFieldCategories}
|
||||||
<SelectTrigger>
|
allFields={allFields}
|
||||||
<SelectValue placeholder="Select field..." />
|
onChange={(value: string) => {
|
||||||
</SelectTrigger>
|
if (handleChange) handleChange(value);
|
||||||
<SelectContent>
|
}}
|
||||||
{fieldCategoriesForSelector.map(category => (
|
isFieldMappedToOtherColumn={isFieldMappedToOtherColumn}
|
||||||
<div key={category.name}>
|
handleCommandListWheel={handleCommandListWheel}
|
||||||
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground">
|
/>
|
||||||
{category.name}
|
|
||||||
</div>
|
|
||||||
{category.fields.map(field => (
|
|
||||||
<SelectItem key={field.key as string} value={field.key as string}>
|
|
||||||
{field.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
<Separator className="my-1" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
);
|
);
|
||||||
}, [availableFieldCategories, allFieldCategories, columnChangeHandlers]);
|
}, [availableFieldCategories, allFields, columnChangeHandlers, isFieldMappedToOtherColumn, handleCommandListWheel]);
|
||||||
|
|
||||||
// Replace the renderValueMappings function with a memoized version
|
// Replace the renderValueMappings function with a memoized version
|
||||||
const renderValueMappings = useCallback((column: Column<T>) => {
|
const renderValueMappings = useCallback((column: Column<T>) => {
|
||||||
@@ -1023,8 +1371,8 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="sticky top-0 bg-muted z-10">
|
<TableHeader className="sticky top-0 bg-muted z-10">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-1/3">Spreadsheet Column</TableHead>
|
<TableHead className="w-1/4">Imported Spreadsheet Column</TableHead>
|
||||||
<TableHead className="w-12 text-center">Data</TableHead>
|
<TableHead className="w-15 text-center">Sample Data</TableHead>
|
||||||
<TableHead className="w-12"></TableHead>
|
<TableHead className="w-12"></TableHead>
|
||||||
<TableHead>Map To Field</TableHead>
|
<TableHead>Map To Field</TableHead>
|
||||||
<TableHead className="w-24 text-right">Ignore</TableHead>
|
<TableHead className="w-24 text-right">Ignore</TableHead>
|
||||||
@@ -1216,97 +1564,52 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm font-medium">Supplier</label>
|
<label className="text-sm font-medium">Supplier</label>
|
||||||
<Select
|
<SupplierSelector
|
||||||
value={globalSelections.supplier}
|
value={globalSelections.supplier}
|
||||||
onValueChange={(value) => setGlobalSelections(prev => ({ ...prev, supplier: value }))}
|
onChange={(value) => setGlobalSelections(prev => ({ ...prev, supplier: value }))}
|
||||||
>
|
suppliers={fieldOptions?.suppliers || []}
|
||||||
<SelectTrigger>
|
/>
|
||||||
<SelectValue placeholder="Select supplier..." />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{hasSuppliers(stableFieldOptions) ?
|
|
||||||
stableFieldOptions.suppliers.map((supplier: any) => (
|
|
||||||
<SelectItem key={supplier.value} value={supplier.value}>
|
|
||||||
{supplier.label}
|
|
||||||
</SelectItem>
|
|
||||||
)) : null}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm font-medium">Company</label>
|
<label className="text-sm font-medium">Company</label>
|
||||||
<Select
|
<CompanySelector
|
||||||
value={globalSelections.company}
|
value={globalSelections.company}
|
||||||
onValueChange={(value) => {
|
onChange={(value) => setGlobalSelections(prev => ({
|
||||||
setGlobalSelections(prev => ({
|
|
||||||
...prev,
|
...prev,
|
||||||
company: value,
|
company: value,
|
||||||
line: undefined,
|
line: undefined,
|
||||||
subline: undefined
|
subline: undefined
|
||||||
}))
|
}))}
|
||||||
}}
|
companies={fieldOptions?.companies || []}
|
||||||
>
|
/>
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select company..." />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{hasCompanies(stableFieldOptions) ?
|
|
||||||
stableFieldOptions.companies.map((company: any) => (
|
|
||||||
<SelectItem key={company.value} value={company.value}>
|
|
||||||
{company.label}
|
|
||||||
</SelectItem>
|
|
||||||
)) : null}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm font-medium">Line</label>
|
<label className="text-sm font-medium">Line</label>
|
||||||
<Select
|
<LineSelector
|
||||||
value={globalSelections.line}
|
value={globalSelections.line}
|
||||||
onValueChange={(value) => {
|
onChange={(value) => setGlobalSelections(prev => ({
|
||||||
setGlobalSelections(prev => ({
|
|
||||||
...prev,
|
...prev,
|
||||||
line: value,
|
line: value,
|
||||||
subline: undefined
|
subline: undefined
|
||||||
}))
|
}))}
|
||||||
}}
|
lines={productLines || []}
|
||||||
disabled={!globalSelections.company}
|
disabled={!globalSelections.company}
|
||||||
>
|
/>
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select line..." />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Array.isArray(stableProductLines) ?
|
|
||||||
stableProductLines.map((line: any) => (
|
|
||||||
<SelectItem key={line.value} value={line.value}>
|
|
||||||
{line.label}
|
|
||||||
</SelectItem>
|
|
||||||
)) : null}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm font-medium">Sub Line</label>
|
<label className="text-sm font-medium">Sub Line</label>
|
||||||
<Select
|
<SubLineSelector
|
||||||
value={globalSelections.subline}
|
value={globalSelections.subline}
|
||||||
onValueChange={(value) => setGlobalSelections(prev => ({ ...prev, subline: value }))}
|
onChange={(value) => setGlobalSelections(prev => ({
|
||||||
|
...prev,
|
||||||
|
subline: value
|
||||||
|
}))}
|
||||||
|
sublines={sublines || []}
|
||||||
disabled={!globalSelections.line}
|
disabled={!globalSelections.line}
|
||||||
>
|
/>
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select sub line..." />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Array.isArray(stableSublines) ?
|
|
||||||
stableSublines.map((subline: any) => (
|
|
||||||
<SelectItem key={subline.value} value={subline.value}>
|
|
||||||
{subline.label}
|
|
||||||
</SelectItem>
|
|
||||||
)) : null}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1378,8 +1681,8 @@ export const MatchColumnsStep = React.memo(<T extends string>({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setShowAllColumns(!showAllColumns)}
|
onClick={() => setShowAllColumns(!showAllColumns)}
|
||||||
>
|
>
|
||||||
{showAllColumns ? <EyeOffIcon className="h-3.5 w-3.5 mr-1" /> : <EyeIcon className="h-3.5 w-3.5 mr-1" />}
|
{!showAllColumns ? <EyeIcon className="h-3.5 w-3.5 mr-1" /> : <EyeOffIcon className="h-3.5 w-3.5 mr-1" />}
|
||||||
{showAllColumns ? "Hide mapped" : "Show all"}
|
{!showAllColumns ? "Show all" : "Hide mapped"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const translations = {
|
|||||||
filterSwitchTitle: "Show only rows with errors",
|
filterSwitchTitle: "Show only rows with errors",
|
||||||
},
|
},
|
||||||
imageUploadStep: {
|
imageUploadStep: {
|
||||||
title: "Add Product Images",
|
title: "Add Images",
|
||||||
nextButtonTitle: "Submit",
|
nextButtonTitle: "Submit",
|
||||||
backButtonTitle: "Back",
|
backButtonTitle: "Back",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user