freshyo/apps/admin-ui/components/ProductsSelector.tsx
2026-03-09 21:05:58 +05:30

189 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
testID="product-groups-dropdown"
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>
);
}