freshyo/apps/admin-ui/components/ProductsSelector.tsx
2026-08-15 00:30:04 +05:30

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>
);
}