177 lines
5.5 KiB
TypeScript
177 lines
5.5 KiB
TypeScript
import React, { useState, useMemo } from 'react';
|
|
import { View } from 'react-native';
|
|
import BottomDropdown, { DropdownOption } from 'common-ui/src/components/bottom-dropdown';
|
|
import { trpc } from '../src/trpc-client';
|
|
import { tw } from 'common-ui';
|
|
|
|
interface SkuSummary {
|
|
id: number;
|
|
productId: number;
|
|
productName: string;
|
|
label: string;
|
|
storeId: number | null;
|
|
price: string;
|
|
}
|
|
|
|
interface Group {
|
|
id: number;
|
|
groupName: string;
|
|
products: { id: number }[];
|
|
}
|
|
|
|
interface ProductsSelectorProps {
|
|
value: number | number[];
|
|
onChange: (value: number | number[]) => void;
|
|
multiple?: boolean;
|
|
label?: string;
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
error?: boolean;
|
|
isDisabled?: (product: SkuSummary) => boolean;
|
|
labelFormat?: (product: SkuSummary) => string;
|
|
groups?: Group[];
|
|
selectedGroupIds?: number[];
|
|
onGroupChange?: (groupIds: number[]) => void;
|
|
showGroups?: boolean;
|
|
}
|
|
|
|
export default function ProductsSelector({
|
|
value,
|
|
onChange,
|
|
multiple = true,
|
|
label = 'Select Products',
|
|
placeholder = 'Select products',
|
|
disabled = false,
|
|
error = false,
|
|
isDisabled,
|
|
labelFormat,
|
|
groups = [],
|
|
showGroups = true,
|
|
selectedGroupIds = [],
|
|
onGroupChange,
|
|
}: ProductsSelectorProps) {
|
|
const { data: skusData } = trpc.common.product.getAllSkusSummary.useQuery({});
|
|
const allSkus: SkuSummary[] = skusData?.skus || [];
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
|
|
// Handle group selection changes
|
|
const handleGroupChange = (newGroupIds: number[]) => {
|
|
if (!onGroupChange) return;
|
|
|
|
const previousGroupIds = selectedGroupIds;
|
|
|
|
const addedGroups = newGroupIds.filter(id => !previousGroupIds.includes(id));
|
|
const removedGroups = previousGroupIds.filter(id => !newGroupIds.includes(id));
|
|
|
|
let currentSkus = Array.isArray(value) ? [...value] : value ? [value] : [];
|
|
|
|
const addedSkus = addedGroups.flatMap(groupId => {
|
|
const group = groups.find(g => g.id === groupId);
|
|
if (!group) return [];
|
|
const productIds = new Set(group.products.map(p => p.id));
|
|
return allSkus.filter(s => productIds.has(s.productId)).map(s => s.id);
|
|
});
|
|
|
|
const removedSkus = removedGroups.flatMap(groupId => {
|
|
const group = groups.find(g => g.id === groupId);
|
|
if (!group) return [];
|
|
const productIds = new Set(group.products.map(p => p.id));
|
|
return allSkus.filter(s => productIds.has(s.productId)).map(s => s.id);
|
|
});
|
|
|
|
currentSkus = [...new Set([...currentSkus, ...addedSkus])];
|
|
currentSkus = currentSkus.filter(id => !removedSkus.includes(id));
|
|
|
|
onGroupChange(newGroupIds);
|
|
if (multiple) {
|
|
onChange(currentSkus.length > 0 ? currentSkus : []);
|
|
} else {
|
|
onChange(currentSkus.length > 0 ? currentSkus[0] : 0);
|
|
}
|
|
};
|
|
|
|
// Filter products based on search query
|
|
const filteredSkus = useMemo(() => {
|
|
if (!searchQuery.trim()) return allSkus;
|
|
|
|
const query = searchQuery.toLowerCase();
|
|
return allSkus.filter(sku =>
|
|
sku.label.toLowerCase().includes(query) ||
|
|
sku.productName.toLowerCase().includes(query)
|
|
);
|
|
}, [allSkus, searchQuery]);
|
|
|
|
// Build dropdown options
|
|
const productOptions: DropdownOption[] = useMemo(() => {
|
|
return filteredSkus.map((sku) => {
|
|
const isFromGroup = selectedGroupIds.length > 0 && groups.some(group => {
|
|
if (!selectedGroupIds.includes(group.id)) return false;
|
|
return group.products.some(p => p.id === sku.productId);
|
|
});
|
|
|
|
const isProductDisabled = isDisabled ? isDisabled(sku) : false;
|
|
|
|
const displayLabel = labelFormat
|
|
? labelFormat(sku)
|
|
: sku.label;
|
|
|
|
return {
|
|
label: `${displayLabel}${isFromGroup ? ' (from group)' : ''}`,
|
|
value: sku.id.toString(),
|
|
disabled: isProductDisabled,
|
|
};
|
|
});
|
|
}, [filteredSkus, selectedGroupIds, groups, isDisabled, labelFormat]);
|
|
|
|
// Build group options if groups are provided
|
|
const groupOptions: DropdownOption[] = useMemo(() => {
|
|
return groups.map(group => ({
|
|
label: group.groupName,
|
|
value: group.id.toString(),
|
|
}));
|
|
}, [groups]);
|
|
|
|
return (
|
|
<View style={tw`w-full`}>
|
|
{showGroups && groups.length > 0 && (
|
|
<View style={tw`mb-4`}>
|
|
<BottomDropdown
|
|
testID="product-groups-dropdown"
|
|
label="Select Product Groups"
|
|
options={groupOptions}
|
|
value={selectedGroupIds.map(id => id.toString())}
|
|
onValueChange={(selectedValue) => {
|
|
const selectedValues = Array.isArray(selectedValue) ? selectedValue : typeof selectedValue === 'string' ? [selectedValue] : [];
|
|
const newGroupIds = selectedValues.map(v => parseInt(v as string));
|
|
handleGroupChange(newGroupIds);
|
|
}}
|
|
placeholder="Select product groups (optional)"
|
|
multiple={true}
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
<BottomDropdown
|
|
label={label}
|
|
options={productOptions}
|
|
value={multiple
|
|
? (Array.isArray(value) ? value.map(id => id.toString()) : [])
|
|
: (value ? value.toString() : '')
|
|
}
|
|
onValueChange={(selectedValue) => {
|
|
if (multiple) {
|
|
const selectedValues = Array.isArray(selectedValue) ? selectedValue : typeof selectedValue === 'string' ? [selectedValue] : [];
|
|
onChange(selectedValues.map(v => Number(v)));
|
|
} else {
|
|
onChange(Number(selectedValue));
|
|
}
|
|
}}
|
|
placeholder={placeholder}
|
|
multiple={multiple}
|
|
disabled={disabled}
|
|
error={error}
|
|
onSearch={setSearchQuery}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|