best-doctor/ke

View on GitHub
src/DetailView/RenderDetail.tsx

Summary

Maintainability
B
5 hrs
Test Coverage
D
62%
// Это легаси
/* eslint-disable react/jsx-props-no-spreading */
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useToast, Box, Spinner, Alert, AlertTitle, AlertIcon, AlertDescription } from '@chakra-ui/react'
import { useParams } from 'react-router-dom'
import { Row, Col } from 'react-flexbox-grid'

import type { BaseAdmin } from 'admin'
import type { Provider } from 'admin/providers/interfaces'
import type { BaseAnalytic } from 'integration/analytics/base'
import type { FieldsTypeInAdminClass, WizardControl } from 'typing'

import { mountWizards } from '../WizardMaster/mountWizards'
import { mountWizards as updatedMountWizards } from '../Wizard/mountWizards'
import { mountDetailFields } from './mountDetailFields'
import { BaseNotifier, ChakraUINotifier } from '../common/notifier'
import { ToListViewLink } from './components/ToListViewLink'
import { setFavicon } from '../Browser/Favicon'
import { ErrorBoundary } from '../common/components/ErrorBoundary'
import { containerStore } from './store'
import { setInitialValue } from './events'
import { useCreateTestId } from '../django-spa/aspects'
import { SaveEventProvider } from './SaveEvent/SaveEventProvider'

const ViewType = 'detail_view'

type BackendResourceName = string

type RenderDetailProps = {
  resourceName: BackendResourceName
  admin: BaseAdmin
  provider: Provider
  user: object
  analytics?: BaseAnalytic
  notifier?: BaseNotifier
}

const getContainersToMount = (): { [key in FieldsTypeInAdminClass]: Function } =>
  /*
    Takes a handler that will embed objects for a specific type of fields.

    Types of fields can be viewed in BaseAdmin class
  */
  ({
    detail_fields: mountDetailFields,
    wizards: mountWizards,
    updated_wizards: updatedMountWizards,
    additional_detail_widgets: mountDetailFields,
  })

const RenderDetail = (props: RenderDetailProps): JSX.Element => {
  /*
    Entry point for displaying components in https://myspa.com/some-url/100500 route format.

    Here we fetch data from the backend using the url that we specified in a
    admin class.

    After that we mounts the widgets of a particular view type. At the moment there are two:
    - Detail View (see mountDetailFields for detail)
    - Wizard View (see mountWizards for detail)
  */
  const [mainDetailObject, setMainDetailObject] = useState<Model>()
  const [needRefreshDetailObject, setNeedRefreshDetailObject] = useState<boolean>(true)
  const { id } = useParams<{ id: string }>()
  const { resourceName, admin, provider, notifier } = props
  const toast = useToast()
  const detailNotifier = notifier || new ChakraUINotifier(toast)
  const [isLoading, setIsLoading] = useState<boolean>(true)
  const [loadError, setLoadError] = useState<LoadError | null>(null)
  const activeWizardRef = useRef<WizardControl>()

  let title = `${admin.verboseName} # ${id}`
  if (admin.getPageTitle) {
    const pageTitle = admin.getPageTitle(mainDetailObject)
    if (pageTitle) {
      title = pageTitle
    }
  }
  document.title = title

  let favicon = admin.favicon || ''

  if (admin.getPageFavicon) {
    const favIconSource = admin.getPageFavicon(mainDetailObject)
    if (favIconSource) {
      favicon = favIconSource
    }
  }
  setFavicon(favicon)

  const refreshMainDetailObject = useCallback(() => {
    setNeedRefreshDetailObject(true)
  }, [])

  // update data if id has changed
  useEffect(() => {
    refreshMainDetailObject()
  }, [id, refreshMainDetailObject])

  useEffect(() => {
    if (needRefreshDetailObject) {
      const backendResourceUrl = admin.getResource(id)

      provider
        .getObject(backendResourceUrl)
        .then(async (res) => {
          setNeedRefreshDetailObject(false)
          setMainDetailObject(res)
          if (admin?.onDetailObjectLoaded !== undefined) {
            await admin.onDetailObjectLoaded({
              mainDetailObject: res,
              provider,
              context: containerStore,
              setInitialValue,
            })
          }
        })
        .catch((er: LoadError) => {
          setLoadError(er)
        })
        .finally(() => setIsLoading(false))
    }
  }, [id, provider, admin, needRefreshDetailObject, props, mainDetailObject])

  const { getDataTestId } = useCreateTestId({ name: admin.name })

  useEffect(() => {
    admin.onMount()
    return () => admin.onUnmount()
  }, [admin])

  return (
    <SaveEventProvider>
      <Row>
        <Col xs={12} xsOffset={0} md={10} mdOffset={1}>
          <Box padding="8px 0px">
            <ToListViewLink name={resourceName} />
          </Box>
        </Col>
      </Row>
      <Row {...getDataTestId()}>
        <Col xs={12} xsOffset={0} md={10} mdOffset={1}>
          {isLoading ? <Spinner /> : ''}
          {!isLoading && !loadError
            ? Object.entries(getContainersToMount()).map(([elementsKey, container]: [string, Function]) => {
                const elements = admin[elementsKey as keyof typeof admin]
                if (!elements) return []

                return (
                  <ErrorBoundary>
                    {container({
                      mainDetailObject,
                      setMainDetailObject,
                      ViewType,
                      elements,
                      elementsKey,
                      refreshMainDetailObject,
                      activeWizardRef,
                      ...props,
                      notifier: detailNotifier,
                    })}
                  </ErrorBoundary>
                )
              })
            : ''}
          {!isLoading && loadError ? (
            <Alert status="error" {...getDataTestId({ postfix: '--loadingError' })}>
              <AlertIcon />
              <AlertTitle mr={2}>Ошибка при выполнении запроса</AlertTitle>
              <AlertDescription>{loadError.response?.data?.message}</AlertDescription>
            </Alert>
          ) : (
            ''
          )}
        </Col>
      </Row>
    </SaveEventProvider>
  )
}

interface LoadError extends Error {
  response?: {
    data?: {
      message?: string
    }
  }
}

export { RenderDetail }