best-doctor/ke

View on GitHub
src/DetailView/widgets/SelectWidget.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
A
95%
// Это легаси
/* eslint-disable react/jsx-props-no-spreading */
import React, { forwardRef, useEffect, useState } from 'react'
import Select, { StylesConfig, ValueType } from 'react-select'
import type { Store } from 'effector'

import { BoxProps } from '@chakra-ui/react'
import { WidgetWrapper } from '../../common/components/WidgetWrapper'
import {
  getAccessor,
  getData,
  getPayload,
  getWidgetContent,
  applyCallback,
  getAccessorWithDefault,
} from '../utils/dataAccess'
import { EventNameEnum, WidgetTypeEnum, pushAnalytics } from '../../integration/analytics'

import type { GenericAccessor, DetailObject, WidgetProps, Accessor, ValueOrPromise } from '../../typing'
import { components, modifyStyles } from '../../common/components/ReactSelectCustomization'
import { useCreateTestId } from '../../django-spa/aspects'

export type SelectObject = {
  value: string
  text: string
}

type BaseSelectWidgetProps = {
  name: string
  mainDetailObject: DetailObject
  helpText?: string
  description?: string | JSX.Element
  displayValue?: GenericAccessor
  data: Accessor<ValueOrPromise<SelectObject[]>>
  style?: object
  setInitialValue: Function
  handleChange: Function
  containerStore: Store<object>
  required?: Accessor<boolean>
  isDisabled?: boolean
  isClearable?: boolean
  placeholder?: string
  labelContainerProps?: BoxProps
  containerProps?: BoxProps
  widgetStyles?: StylesConfig<any, false>
}

const getSelectContent = (
  name: string,
  detailObject: DetailObject,
  displayValue: GenericAccessor,
  context: {}
): [string, string] => {
  // TODO Remove this
  try {
    const { value, text } = getWidgetContent(name, detailObject, displayValue, context)
    return [value, text]
  } catch (TypeError) {
    return ['', '']
  }
}

const BaseSelectWidget = forwardRef<HTMLSelectElement, BaseSelectWidgetProps>(
  (props: BaseSelectWidgetProps, ref): JSX.Element => {
    const {
      name,
      helpText,
      description,
      displayValue,
      containerStore,
      mainDetailObject,
      data,
      style,
      setInitialValue,
      handleChange,
      required,
      isDisabled = false,
      isClearable = false,
      placeholder = 'Выберите значение',
      containerProps,
      labelContainerProps,
      widgetStyles: extenrnalStyles,
    } = props

    const context = containerStore.getState()

    const [value, label] = getSelectContent(name, mainDetailObject, displayValue, context)
    const isRequired = getAccessorWithDefault(required, mainDetailObject, context, false)

    const [resultOptions, setResultOptions] = useState<SelectObject[]>([])
    setInitialValue({ [name]: value })

    const widgetStyles = {
      menuPortal: (base: object) => ({ ...base, zIndex: 9999 }),
      ...extenrnalStyles,
    }

    useEffect(() => {
      const responseOptions = getAccessor(data, mainDetailObject, context)
      return applyCallback(responseOptions, setResultOptions)
    }, [data, mainDetailObject, context])

    const formatOption = (option: { value: any; label?: string; text?: string }): { value: any; label: string } => ({
      value: option.value,
      label: option?.label || option?.text || '',
    })

    const options = React.useMemo(() => resultOptions.map((option) => formatOption(option)), [resultOptions])

    const { getDataTestId } = useCreateTestId()

    return (
      <WidgetWrapper
        name={name}
        style={style}
        helpText={helpText}
        description={description}
        required={isRequired}
        containerProps={containerProps}
        labelContainerProps={labelContainerProps}
        {...getDataTestId(props)}
      >
        <Select
          inputRef={ref}
          options={options}
          defaultValue={value ? { value, label } : undefined}
          onChange={(changeValue: ValueType<object | object[], boolean>) => handleChange(changeValue)}
          styles={modifyStyles(widgetStyles)}
          isDisabled={isDisabled}
          components={components}
          isClearable={isClearable}
          placeholder={placeholder}
          name={name}
        />
      </WidgetWrapper>
    )
  }
)

interface SelectWidgetProps extends WidgetProps {
  widgetStyles?: StylesConfig<any, false>
}

const SelectWidget = forwardRef<HTMLSelectElement, SelectWidgetProps>((props: SelectWidgetProps, ref): JSX.Element => {
  const {
    name,
    displayValue,
    containerStore,
    mainDetailObject,
    dataSource,
    dataTarget,
    targetPayload,
    provider,
    setInitialValue,
    submitChange,
    cacheTime,
  } = props
  const targetUrl = getData(dataTarget, mainDetailObject) || mainDetailObject.url
  const context = containerStore.getState()
  const effectiveCacheTime = getAccessor(cacheTime, mainDetailObject, context)

  const [value] = getSelectContent(name, mainDetailObject, displayValue, context)

  setInitialValue({ [name]: value })

  const options = (): Promise<SelectObject[]> => {
    const sourceUrl = getData(dataSource, mainDetailObject, context)
    return provider
      .getPage(sourceUrl, undefined, undefined, effectiveCacheTime)
      .then(([responseOptions, ,]: [any, object, object]) => responseOptions as SelectObject[])
  }

  const handleChange = (changeValue: SelectObject): void => {
    const widgetPayload = getPayload(changeValue?.value || null, name, targetPayload)

    pushAnalytics({
      eventName: EventNameEnum.SELECT_OPTION_CHANGE,
      widgetType: WidgetTypeEnum.INPUT,
      value: changeValue,
      objectForAnalytics: props.mainDetailObject,
      ...props,
    })

    submitChange({ url: targetUrl, payload: widgetPayload })
  }

  return <BaseSelectWidget ref={ref} data={options} handleChange={handleChange} {...props} />
})

export { SelectWidget, BaseSelectWidget }