import PropTypes from "prop-types"; import { Box, ListItem, Autocomplete, TextField, Typography, Checkbox, } from "@mui/material"; import { useTheme } from "@emotion/react"; import SearchIcon from "../../../assets/icons/search.svg?react"; import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; import FieldWrapper from "../FieldWrapper"; /** * Search component using Material UI's Autocomplete. * * @param {Object} props * @param {string} props.id - Unique identifier for the Autocomplete component * @param {Array} props.options - Options to display in the Autocomplete dropdown * @param {string} props.filteredBy - Key to access the option label from the options * @param {string} props.value - Current input value for the Autocomplete * @param {Function} props.handleChange - Function to call when the input changes * @param {Function} Prop.onBlur - Function to call when the input is blured * @param {Object} props.sx - Additional styles to apply to the component * @param {string} props.unit - Label to identify type of options * @returns {JSX.Element} The rendered Search component */ const SearchAdornment = () => { const theme = useTheme(); return ( ); }; //TODO keep search state inside of component. const Search = ({ label, id, options, filteredBy, secondaryLabel, value, inputValue, handleInputChange, handleChange, sx, multiple = false, isAdorned = true, error, disabled, startAdornment, endAdornment, onBlur, //FieldWrapper's props gap, labelMb, labelFontWeight, labelVariant, labelSx = {}, unit = "option", maxWidth = "100%", }) => { const theme = useTheme(); const { t } = useTranslation(); const [selectAll, setSelectAll] = React.useState(false); const [open, setOpen] = React.useState(false); const enhancedOptions = React.useMemo(() => { return multiple && isAdorned ? [ { [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" }, ...options, ] : options; }, [multiple, isAdorned, options, filteredBy]); const isOptionSelected = (option) => { if (!multiple && !isAdorned) return false; if (Array.isArray(value)) { return value.some((item) => item._id === option._id); } return false; }; const handleSelectAll = (isSelectAll) => { const newValue = isSelectAll ? [...options] : []; handleChange(newValue); setSelectAll(isSelectAll); }; useEffect(() => { const allSelected = Array.isArray(value) && Array.isArray(options) && value.length === options.length; if (selectAll !== allSelected) setSelectAll(allSelected); }, [value, options]); return ( setOpen(true)} onClose={(event, reason) => { if (reason === "blur" || reason === "escape") { setOpen(false); } }} inputValue={inputValue} onInputChange={(_, newValue) => { handleInputChange(newValue); }} onChange={(_, newValue) => { if (multiple && isAdorned) { const hasSelectAllSelected = Array.isArray(newValue) && newValue.some((item) => item.isSelectAll); if (hasSelectAllSelected) { handleSelectAll(!selectAll); } else { handleChange(newValue); setSelectAll(Array.isArray(newValue) && newValue.length === options.length); } } else { handleChange(newValue); setOpen(false); } }} fullWidth freeSolo disabled={disabled} disableClearable options={enhancedOptions} getOptionLabel={(option) => option[filteredBy]} isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier renderInput={(params) => ( }), ...(startAdornment && { startAdornment: startAdornment }), ...(endAdornment && { endAdornment: endAdornment }), }, }} sx={{}} /> {error && ( {error} )} )} filterOptions={(options, { inputValue }) => { if (inputValue.trim() === "" && multiple && isAdorned) { return enhancedOptions; } const filtered = options.filter((option) => option[filteredBy].toLowerCase().includes(inputValue.toLowerCase()) ); if (filtered.length === 0) { return [ { [filteredBy]: t("general.noOptionsFound", { unit: unit }), noOptions: true, }, ]; } return filtered; }} getOptionKey={(option) => { return option._id; }} renderOption={(props, option) => { const { key, ...optionProps } = props; const hasSecondaryLabel = secondaryLabel && option[secondaryLabel] !== undefined; const port = option["port"]; const selected = isOptionSelected(option); return ( {multiple && isAdorned && !option.noOptions && ( )} {option[filteredBy] + (hasSecondaryLabel ? ` (${option[secondaryLabel]}${port ? `: ${port}` : ""})` : "")} ); }} slotProps={{ popper: { keepMounted: true, sx: { "& ul": { p: 2, backgroundColor: theme.palette.primary.main }, "& li.MuiAutocomplete-option": { color: theme.palette.primary.contrastTextSecondary, px: 4, borderRadius: theme.shape.borderRadius, }, "& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover": { backgroundColor: theme.palette.primary.lowContrast, color: "red", }, "& li.MuiAutocomplete-option:hover:not([aria-selected='true'])": { color: theme.palette.secondary.contrastText, backgroundColor: theme.palette.secondary.main, }, "& .MuiAutocomplete-noOptions": { px: theme.spacing(6), py: theme.spacing(5), }, }, }, }} sx={{ /* height: 34,*/ "&.MuiAutocomplete-root .MuiAutocomplete-input": { padding: `0 ${theme.spacing(5)}`, }, ...sx, }} /> ); }; Search.propTypes = { label: PropTypes.string, id: PropTypes.string, multiple: PropTypes.bool, options: PropTypes.array.isRequired, filteredBy: PropTypes.string.isRequired, secondaryLabel: PropTypes.string, value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), inputValue: PropTypes.string.isRequired, handleInputChange: PropTypes.func.isRequired, handleChange: PropTypes.func, isAdorned: PropTypes.bool, sx: PropTypes.object, error: PropTypes.string, disabled: PropTypes.bool, startAdornment: PropTypes.object, endAdornment: PropTypes.object, onBlur: PropTypes.func, unit: PropTypes.string, }; export default Search;