feat: searchable product select and department select
This commit is contained in:
@@ -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;
|
||||
|
||||
50
src/components/ProductSelect/utils/getRenderOptions.tsx
Normal file
50
src/components/ProductSelect/utils/getRenderOptions.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user