188 lines
6.1 KiB
TypeScript
188 lines
6.1 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 Product {
|
|
id: number;
|
|
name: string;
|
|
price: number;
|
|
unit?: string;
|
|
shortDescription?: string | null;
|
|
isOutOfStock?: boolean;
|
|
isSuspended?: boolean;
|
|
storeId?: number | null;
|
|
unitNotation?: string;
|
|
}
|
|
|
|
interface Group {
|
|
id: number;
|
|
groupName: string;
|
|
products: Product[];
|
|
}
|
|
|
|
interface ProductsSelectorProps {
|
|
value: number | number[];
|
|
onChange: (value: number | number[]) => void;
|
|
multiple?: boolean;
|
|
label?: string;
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
error?: boolean;
|
|
isDisabled?: (product: Product) => boolean;
|
|
labelFormat?: (product: Product) => 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: productsData } = trpc.common.product.getAllProductsSummary.useQuery({});
|
|
const products = productsData?.products || [];
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
|
|
// Format product label: name (unit) (₹price)
|
|
const formatProductLabel = (product: Product): string => {
|
|
if (labelFormat) {
|
|
return labelFormat(product);
|
|
}
|
|
const unit = product.unit ? ` (${product.unit})` : '';
|
|
const price = ` (₹${product.price})`;
|
|
return `${product.name}${unit}${price}`;
|
|
};
|
|
|
|
// Handle group selection changes
|
|
const handleGroupChange = (newGroupIds: number[]) => {
|
|
if (!onGroupChange) return;
|
|
|
|
const previousGroupIds = selectedGroupIds;
|
|
|
|
// Find which groups were added and which were removed
|
|
const addedGroups = newGroupIds.filter(id => !previousGroupIds.includes(id));
|
|
const removedGroups = previousGroupIds.filter(id => !newGroupIds.includes(id));
|
|
|
|
// Get current selected products
|
|
let currentProducts = Array.isArray(value) ? [...value] : value ? [value] : [];
|
|
|
|
// Add products from newly selected groups
|
|
const addedProducts = addedGroups.flatMap(groupId => {
|
|
const group = groups.find(g => g.id === groupId);
|
|
return group?.products.map(p => p.id) || [];
|
|
});
|
|
|
|
// Remove products from deselected groups
|
|
const removedProducts = removedGroups.flatMap(groupId => {
|
|
const group = groups.find(g => g.id === groupId);
|
|
return group?.products.map(p => p.id) || [];
|
|
});
|
|
|
|
// Update product list: add new ones, remove deselected group ones
|
|
currentProducts = [...new Set([...currentProducts, ...addedProducts])];
|
|
currentProducts = currentProducts.filter(id => !removedProducts.includes(id));
|
|
|
|
onGroupChange(newGroupIds);
|
|
if (multiple) {
|
|
onChange(currentProducts.length > 0 ? currentProducts : []);
|
|
} else {
|
|
onChange(currentProducts.length > 0 ? currentProducts[0] : 0);
|
|
}
|
|
};
|
|
|
|
// Filter products based on search query
|
|
const filteredProducts = useMemo(() => {
|
|
if (!searchQuery.trim()) return products;
|
|
|
|
const query = searchQuery.toLowerCase();
|
|
return products.filter(product =>
|
|
product.name.toLowerCase().includes(query) ||
|
|
(product.shortDescription && product.shortDescription.toLowerCase().includes(query)) ||
|
|
(product.unit && product.unit.toLowerCase().includes(query))
|
|
);
|
|
}, [products, searchQuery]);
|
|
|
|
// Build dropdown options
|
|
const productOptions: DropdownOption[] = useMemo(() => {
|
|
return filteredProducts.map((product) => {
|
|
const isFromGroup = selectedGroupIds.length > 0 && groups.some(group =>
|
|
selectedGroupIds.includes(group.id) && group.products.some(p => p.id === product.id)
|
|
);
|
|
|
|
const isProductDisabled = isDisabled ? isDisabled(product as Product) : false;
|
|
|
|
return {
|
|
label: `${formatProductLabel(product as Product)}${isFromGroup ? ' (from group)' : ''}`,
|
|
value: product.id.toString(),
|
|
disabled: isProductDisabled,
|
|
};
|
|
});
|
|
}, [filteredProducts, 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`}>
|
|
{/* Groups selector (if groups are provided and showGroups is true) */}
|
|
{showGroups && groups.length > 0 && (
|
|
<View style={tw`mb-4`}>
|
|
<BottomDropdown
|
|
label="Select Product Groups"
|
|
options={groupOptions}
|
|
value={selectedGroupIds.map(id => id.toString())}
|
|
onValueChange={(value) => {
|
|
const selectedValues = Array.isArray(value) ? value : typeof value === 'string' ? [value] : [];
|
|
const newGroupIds = selectedValues.map(v => parseInt(v as string));
|
|
handleGroupChange(newGroupIds);
|
|
}}
|
|
placeholder="Select product groups (optional)"
|
|
multiple={true}
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
{/* Products selector */}
|
|
<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>
|
|
);
|
|
}
|