feat: searchable product select and department select

This commit is contained in:
2025-01-31 20:18:01 +04:00
parent 52619f119f
commit 0a4e20e888
7 changed files with 126 additions and 156 deletions

View File

@@ -2,21 +2,10 @@ import { ProductSchema } from "../../client";
import { FC, useState } from "react";
import useProductsList from "../../pages/ProductsPage/hooks/useProductsList.tsx";
import { omit } from "lodash";
import ObjectSelect, {
ObjectSelectProps,
} from "../ObjectSelect/ObjectSelect.tsx";
import {
ComboboxItem,
Image,
Loader,
OptionsFilter,
rem,
SelectProps,
Text,
Tooltip,
} from "@mantine/core";
import { getProductFields } from "../../types/utils.ts";
import ObjectSelect, { ObjectSelectProps } from "../ObjectSelect/ObjectSelect.tsx";
import { Loader, OptionsFilter } from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks";
import getRenderOptions from "./utils/getRenderOptions.tsx";
type RestProps = {
clientId: number;
@@ -33,68 +22,8 @@ const ProductSelect: FC<Props> = (props: Props) => {
itemsPerPage: MAX_PRODUCTS,
});
const restProps = omit(props, ["clientId"]);
const renderOption: SelectProps["renderOption"] = item => {
const product = products.find(
product => product.id == parseInt(item.option.value)
);
if (!product) return item.option.label;
const productFields = getProductFields(product);
const imageUrl =
product.images && product.images[0]
? product.images[0].imageUrl
: undefined;
return (
<Tooltip
style={{ whiteSpace: "pre-line" }}
multiline
disabled={productFields.length === 0}
label={
<>
{productFields
.map(([key, value]) => {
return `${key.toString()}: ${value.toString()}`;
})
.join("\n")}
{imageUrl && (
<Image
src={imageUrl}
alt={product.name}
maw={rem(250)}
/>
)}
</>
}>
<div>
{product.name}
<br />
{product.barcodes && (
<Text size={"xs"}>{product.barcodes[0]}</Text>
)}
</div>
</Tooltip>
);
};
const optionsFilter: OptionsFilter = ({ options, search }) => {
return options;
const filtered = (options as ComboboxItem[]).filter(option => {
const product = products.find(
product => product.id == parseInt(option.value)
);
if (!product) return true;
return (
product.name.toLowerCase().includes(search.toLowerCase()) ||
product.barcodes.some(value =>
value.toLowerCase().includes(search.toLowerCase())
) ||
product.article?.toLowerCase() === search.toLowerCase()
);
});
filtered.sort((a, b) => a.label.localeCompare(b.label));
return filtered.length > MAX_PRODUCTS
? filtered.slice(0, MAX_PRODUCTS)
: filtered;
};
const optionsFilter: OptionsFilter = ({ options }) => options;
const setSearchValueImpl = (value: string) => {
const names = products.map(product => product.name);
if (names.includes(value)) return;
@@ -103,14 +32,13 @@ const ProductSelect: FC<Props> = (props: Props) => {
return (
<ObjectSelect
// disabled={isLoading}
rightSection={
isLoading || searchValue !== debounced ? (
<Loader size={"sm"} />
) : null
}
onSearchChange={setSearchValueImpl}
renderOption={renderOption}
renderOption={getRenderOptions(products)}
searchable
{...restProps}
data={products}
@@ -118,58 +46,5 @@ const ProductSelect: FC<Props> = (props: Props) => {
/>
);
};
export default ProductSelect;
// type ControlledValueProps = {
// value: ProductSchema;
// onChange: (value: ProductSchema) => void;
// }
// type RestProps = {
// defaultValue?: ProductSchema;
// onChange: (value: ProductSchema) => void;
// clientId: number;
// }
// type Props = (RestProps & Partial<ControlledValueProps>) & Omit<SelectProps, 'value' | 'onChange'>;
//
// const ProductSelect: FC<Props> = (props) => {
// const isControlled = 'value' in props;
// const [intertalValue, setInternalValue] = useState<ProductSchema | undefined>(props.defaultValue);
// const value = isControlled ? props.value : intertalValue
//
// const {products} = useProductsList({clientId: props.clientId});
//
//
// const data = useMemo(() => products.reduce((acc, product) => {
// acc.push({
// label: product.name,
// value: product.id.toString()
// });
// return acc;
// }, [] as { label: string, value: string }[]), [products]);
//
// const handleOnChange = (event: string | null) => {
// if (!event) return;
// const product = products.find(product => parseInt(event) == product.id);
// if (!product) return;
// if (isControlled) {
// props.onChange(product);
// return;
// }
// setInternalValue(product);
// }
// useEffect(() => {
// if (isControlled || !intertalValue) return;
// props.onChange(intertalValue);
// }, [intertalValue]);
// const restProps = omit(props, ['clientId'])
// return (
// <Select
// {...restProps}
// withCheckIcon={false}
// searchable
// value={value?.id.toString()}
// onChange={handleOnChange}
// data={data}
// />
// )
// }
// export default ProductSelect;

View File

@@ -0,0 +1,50 @@
import { ProductSchema } from "../../../client";
import { ComboboxItem, ComboboxLikeRenderOptionInput, Image, rem, SelectProps, Text, Tooltip } from "@mantine/core";
import { getProductFields } from "../../../types/utils.ts";
const getRenderOptions = (products: ProductSchema[]): SelectProps["renderOption"] => {
return (item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
const product = products.find(
product => product.id == parseInt(item.option.value),
);
if (!product) return item.option.label;
const productFields = getProductFields(product);
const imageUrl =
product.images && product.images[0]
? product.images[0].imageUrl
: undefined;
return (
<Tooltip
style={{ whiteSpace: "pre-line" }}
multiline
disabled={productFields.length === 0}
label={
<>
{productFields
.map(([key, value]) => {
return `${key.toString()}: ${value.toString()}`;
})
.join("\n")}
{imageUrl && (
<Image
src={imageUrl}
alt={product.name}
maw={rem(250)}
/>
)}
</>
}>
<div>
{product.name}
<br />
{product.barcodes && (
<Text size={"xs"}>{product.barcodes[0]}</Text>
)}
</div>
</Tooltip>
);
};
};
export default getRenderOptions;