* add location_id to request_return endpoint to support "receive_now" returns * changeset * admin request return * add locations to recieving returns * cleanup test * add check for inventory service
241 lines
6.2 KiB
TypeScript
241 lines
6.2 KiB
TypeScript
import clsx from "clsx"
|
|
import React, {
|
|
CSSProperties,
|
|
useContext,
|
|
useEffect,
|
|
useImperativeHandle,
|
|
useRef,
|
|
useState,
|
|
} from "react"
|
|
import Primitive from "react-select"
|
|
import AsyncPrimitive from "react-select/async"
|
|
import AsyncCreatablePrimitive from "react-select/async-creatable"
|
|
import CreatablePrimitive from "react-select/creatable"
|
|
import InputHeader, { InputHeaderProps } from "../../fundamentals/input-header"
|
|
import { ModalContext } from "../modal"
|
|
import { SelectComponents } from "./select-components"
|
|
|
|
export type SelectOption<T> = {
|
|
value: T
|
|
label: string
|
|
disabled?: boolean
|
|
}
|
|
|
|
type MultiSelectProps = InputHeaderProps & {
|
|
// component props
|
|
label?: string
|
|
required?: boolean
|
|
name?: string
|
|
className?: string
|
|
fullWidth?: boolean
|
|
// Multiselect props
|
|
placeholder?: string
|
|
isMultiSelect?: boolean
|
|
labelledBy?: string
|
|
menuPortalStyles?: CSSProperties
|
|
options: { label: string; value: string | null; disabled?: boolean }[]
|
|
value:
|
|
| { label: string; value: string }[]
|
|
| { label: string; value: string }
|
|
| null
|
|
filterOptions?: (q: string) => any[]
|
|
hasSelectAll?: boolean
|
|
isLoading?: boolean
|
|
shouldToggleOnHover?: boolean
|
|
onChange: (values: any[] | any) => void
|
|
disabled?: boolean
|
|
enableSearch?: boolean
|
|
isCreatable?: boolean
|
|
clearSelected?: boolean
|
|
onCreateOption?: (value: string) => { value: string; label: string }
|
|
}
|
|
|
|
const SSelect = React.forwardRef(
|
|
(
|
|
{
|
|
label,
|
|
name,
|
|
fullWidth = false,
|
|
required,
|
|
value,
|
|
onChange,
|
|
className,
|
|
isMultiSelect,
|
|
hasSelectAll,
|
|
tooltipContent,
|
|
tooltip,
|
|
enableSearch = true,
|
|
clearSelected = false,
|
|
isCreatable,
|
|
filterOptions,
|
|
placeholder = "Search...",
|
|
options,
|
|
onCreateOption,
|
|
menuPortalStyles = {},
|
|
}: MultiSelectProps,
|
|
ref
|
|
) => {
|
|
const { portalRef } = useContext(ModalContext)
|
|
|
|
const [isFocussed, setIsFocussed] = useState(false)
|
|
const [scrollBlocked, setScrollBlocked] = useState(true)
|
|
|
|
useEffect(() => {
|
|
window.addEventListener("resize", () => {
|
|
setIsFocussed(false)
|
|
selectRef?.current?.blur()
|
|
})
|
|
}, [])
|
|
|
|
const selectRef = useRef(null)
|
|
|
|
useImperativeHandle(ref, () => selectRef.current)
|
|
|
|
const containerRef = useRef(null)
|
|
|
|
const onClickOption = (val, ...args) => {
|
|
if (
|
|
val?.length &&
|
|
val?.find((option) => option.value === "all") &&
|
|
hasSelectAll &&
|
|
isMultiSelect
|
|
) {
|
|
onChange(options)
|
|
} else {
|
|
onChange(val)
|
|
if (!isMultiSelect) {
|
|
selectRef?.current?.blur()
|
|
setIsFocussed(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleOnCreateOption = (val) => {
|
|
if (onCreateOption) {
|
|
onCreateOption(val)
|
|
setIsFocussed(false)
|
|
selectRef?.current?.blur()
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
const delayDebounceFn = setTimeout(() => {
|
|
if (isFocussed) {
|
|
setScrollBlocked(false)
|
|
}
|
|
}, 50)
|
|
|
|
return () => clearTimeout(delayDebounceFn)
|
|
}, [isFocussed])
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className={clsx({
|
|
"w-full": fullWidth,
|
|
})}
|
|
>
|
|
<div
|
|
key={name}
|
|
className={clsx(className, {
|
|
"rounded-t-rounded bg-white": isFocussed,
|
|
})}
|
|
>
|
|
<div className="text-grey-50 pointer-events-none mb-2 flex w-full cursor-pointer justify-between pr-0.5">
|
|
<InputHeader {...{ label, required, tooltip, tooltipContent }} />
|
|
</div>
|
|
|
|
{
|
|
<GetSelect
|
|
isCreatable={isCreatable}
|
|
searchBackend={filterOptions}
|
|
options={
|
|
hasSelectAll && isMultiSelect
|
|
? [{ value: "all", label: "Select All" }, ...options]
|
|
: options
|
|
}
|
|
ref={selectRef}
|
|
value={value}
|
|
isMulti={isMultiSelect}
|
|
openMenuOnFocus={isMultiSelect}
|
|
isSearchable={enableSearch}
|
|
isClearable={clearSelected}
|
|
onChange={onClickOption}
|
|
onMenuOpen={() => {
|
|
setIsFocussed(true)
|
|
}}
|
|
onMenuClose={() => {
|
|
setScrollBlocked(true)
|
|
setIsFocussed(false)
|
|
}}
|
|
closeMenuOnScroll={(e) => {
|
|
if (
|
|
!scrollBlocked &&
|
|
e.target?.contains(containerRef.current) &&
|
|
e.target !== document
|
|
) {
|
|
return true
|
|
}
|
|
}}
|
|
closeMenuOnSelect={!isMultiSelect}
|
|
blurInputOnSelect={!isMultiSelect}
|
|
styles={{
|
|
menuPortal: (base) => ({ ...base, ...menuPortalStyles }),
|
|
}}
|
|
hideSelectedOptions={false}
|
|
menuPortalTarget={portalRef?.current?.lastChild || document.body}
|
|
menuPlacement="auto"
|
|
backspaceRemovesValue={false}
|
|
classNamePrefix="react-select"
|
|
placeholder={placeholder}
|
|
className="react-select-container"
|
|
onCreateOption={handleOnCreateOption}
|
|
components={SelectComponents}
|
|
/>
|
|
}
|
|
{isFocussed && enableSearch && <div className="h-5 w-full" />}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
)
|
|
|
|
const GetSelect = React.forwardRef(
|
|
(
|
|
{ isCreatable, searchBackend, onCreateOption, handleClose, ...props },
|
|
ref
|
|
) => {
|
|
if (isCreatable) {
|
|
return searchBackend ? (
|
|
<AsyncCreatablePrimitive
|
|
ref={ref}
|
|
defaultOptions={true}
|
|
onCreateOption={onCreateOption}
|
|
isSearchable
|
|
loadOptions={searchBackend}
|
|
{...props}
|
|
/>
|
|
) : (
|
|
<CreatablePrimitive
|
|
{...props}
|
|
isSearchable
|
|
ref={ref}
|
|
onCreateOption={onCreateOption}
|
|
/>
|
|
)
|
|
} else if (searchBackend) {
|
|
return (
|
|
<AsyncPrimitive
|
|
ref={ref}
|
|
defaultOptions={true}
|
|
loadOptions={searchBackend}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
return <Primitive ref={ref} {...props} />
|
|
}
|
|
)
|
|
|
|
export default SSelect
|