/* eslint-disable @typescript-eslint/no-explicit-any */ import { useEffect, useState } from "react"; import Select, { StylesConfig, CSSObjectWithLabel } from "react-select"; import CreatableSelect from "react-select/creatable"; import { getList } from "../../Api/MastersApi"; export interface OptionType { value: string | number; label: string; code?: string; name?: string; id?: string | number; } interface SelectOptionsProps { optionsData?: OptionType[]; skillValue?: OptionType | null; apiUrl?: string; className?: string; name: string; id?: string; onChange: ( value: OptionType | OptionType[] | null, name: string, index?: string | number ) => void; value?: OptionType | OptionType[] | null; required?: boolean; isMulti?: boolean; label?: string; addNew?: boolean; stringAdd?: boolean; initValue?: string | number | (string | number)[]; index?: string | number; isclear?: boolean; isDisabled?: boolean; placeholder?: string; borderFlag?: boolean; indicationFlag?: boolean; style?: StylesConfig; classStyleFlag?: boolean; any?: boolean; divClassName?: string; refreshKey?: number; } function SelectOptions({ optionsData, skillValue = null, apiUrl, className = "", name, id, onChange, value = null, required = false, isMulti = false, label = "", addNew = false, stringAdd = false, initValue = "", index = "", isclear = true, isDisabled = false, placeholder = "", style, classStyleFlag = false, any = false, divClassName = "", refreshKey = 0, }: SelectOptionsProps) { const [options, setOptions] = useState([]); const [loading, setLoading] = useState(true); const [inputValue, setInputValue] = useState(""); const [selectedValues, setSelectedValues] = useState< OptionType | OptionType[] | null >(value || (isMulti ? [] : null)); const Multiselect = addNew ? CreatableSelect : Select; const formatLabel = (opt: OptionType): string => { return opt.code && opt.name && opt.code !== opt.name ? `${opt.code} - ${opt.name}` : opt.label || opt.name || opt.code || String(opt.value); }; useEffect(() => { setSelectedValues(value); }, [value]); useEffect(() => { if (optionsData) { setOptions(optionsData); setLoading(false); return; } if (!apiUrl) { setOptions([]); setLoading(false); return; } setLoading(true); getList(`${apiUrl}`, { search: inputValue }) .then((response) => { const { status, data } = response; setLoading(false); if (status) { const arr = Array.isArray(data) ? data : []; const initialOptions: OptionType[] = arr.map( (option: { id: string | number; name: string; code?: string }) => ({ value: option.id, label: formatLabel({ value: option.id, label: option.name, code: option.code, name: option.name, id: option.id, }), code: option.code, name: option.name, id: option.id, }) ); if (initValue !== "" && initValue !== undefined) { const initValues = Array.isArray(initValue) ? initValue : [initValue]; initValues.forEach((val) => { if (!initialOptions.some((option) => option.value === val)) { initialOptions.push({ value: val, label: String(val), }); } }); } setOptions(initialOptions); } }) .catch((error) => { setLoading(false); console.error("Error fetching data:", error); }); }, [apiUrl, initValue, name, optionsData, refreshKey, inputValue]); useEffect(() => { if (skillValue) { setOptions((prev) => { if (!prev.some((opt) => opt.value === skillValue.value)) { return [...prev, skillValue]; } return prev; }); } }, [skillValue]); const selectAllOption: OptionType = { value: !any ? 0 : options.length ? "__ANY__" : 0, label: "Any", }; const isAnySelected = () => { if (!any) return false; if (isMulti) { return Array.isArray(selectedValues) ? selectedValues.some((v) => v.value === selectAllOption.value) : false; } return (selectedValues as OptionType | null)?.value === selectAllOption.value; }; const mappedOptions = options.map((option) => ({ ...option, label: formatLabel(option), id: option.value, })); const mappedValue = (() => { if (isMulti) { if (any && isAnySelected()) { return [selectAllOption]; } return Array.isArray(selectedValues) ? selectedValues.map((val) => ({ ...val, label: formatLabel(val), id: val.value, })) : []; } else if (selectedValues) { if (any && isAnySelected()) { return selectAllOption; } return { ...selectedValues, label: formatLabel(selectedValues as OptionType), id: (selectedValues as OptionType).value, }; } return null; })(); const handleCreateOption = (inputVal: string) => { const exists = options.some( (opt) => opt.value?.toString().toLowerCase() === inputVal.toLowerCase() ); if (exists) return; const newOption: OptionType = { value: inputVal, label: inputVal, }; setOptions((prev) => [...prev, newOption]); if (isMulti) { const updated = Array.isArray(selectedValues) ? [...selectedValues, newOption] : [newOption]; setSelectedValues(updated); onChange(updated, name, index); } else { setSelectedValues(newOption); onChange(newOption, name, index); } }; const handleCreateOptionString = (inputVal: string) => { const newOption: OptionType = { value: inputVal, label: inputVal, code: inputVal, name: inputVal, id: inputVal }; setOptions((prev) => [...prev, newOption]); if (isMulti) { const updated = Array.isArray(selectedValues) ? [...selectedValues, newOption] : [newOption]; setSelectedValues(updated); onChange(updated, name, index); } else { setSelectedValues(newOption); onChange(newOption, name, index); } }; const commonProps: any = { name, id, options: any ? [selectAllOption, ...mappedOptions] : mappedOptions, value: mappedValue, noOptionsMessage: () => (stringAdd ? null : 'No options'), onChange: (selected: unknown) => { setSelectedValues(selected as OptionType | OptionType[] | null); onChange(selected as OptionType | OptionType[] | null, name, index); }, onInputChange: (val: string) => { setInputValue(val); return val; }, onKeyDown: (e: React.KeyboardEvent) => { // if (stringAdd && (e.key === 'Enter' || e.key === 'Tab')) { if (stringAdd && (e.key === 'Enter')) { e.preventDefault(); // Prevent default behavior like form submission if (inputValue.trim()) { handleCreateOptionString(inputValue.trim()); } } }, onBlur: () => { if (stringAdd && inputValue.trim()) { handleCreateOptionString(inputValue.trim()); } }, onMenuClose: () => { if (stringAdd && inputValue.trim()) { handleCreateOptionString(inputValue.trim()); } }, isValidNewOption: () => addNew, onCreateOption: addNew ? handleCreateOption : undefined, required, className: className + " multiple-select01", styles: { control: (base: CSSObjectWithLabel) => ({ ...base, minHeight: "28px", fontSize: "12px", padding: "0px 5px 0px 30px", border: "1px solid #d1d5db", borderRadius: "6px", }), valueContainer: (base: CSSObjectWithLabel) => ({ ...base, padding: "2px 4px", }), input: (base: CSSObjectWithLabel) => ({ ...base, fontSize: "12px", }), dropdownIndicator: (base: CSSObjectWithLabel) => ({ ...base, padding: "2px", }), clearIndicator: (base: CSSObjectWithLabel) => ({ ...base, padding: "2px", }), option: (base: CSSObjectWithLabel, state: any) => ({ ...base, backgroundColor: state.isFocused ? "#f3f4f6" : "white", color: state.isFocused ? "#1f2937" : "#374151", cursor: "pointer", }), ...style, }, placeholder, isMulti, isClearable: isclear, isDisabled, isLoading: loading, menuIsOpen: undefined, getOptionLabel: formatLabel, getOptionValue: (opt: OptionType) => opt.id !== undefined ? String(opt.id) : String(opt.value), filterOption: ( option: { label: string | React.ReactElement; data: OptionType }, input: string ) => { const label = typeof option.label === "string" ? option.label.toLowerCase() : ""; const value = option.data?.code?.toString().toLowerCase() || ""; const name = option.data?.name?.toLowerCase() || ""; const search = input.toLowerCase(); return ( label.includes(search) || value.includes(search) || name.includes(search) ); }, formatCreateLabel: addNew ? (inputVal: string) => `Add "${inputVal}" as a new ${label}` : undefined, }; return ( <> {classStyleFlag && }
); } export default SelectOptions;