serge-web/serge-web

View on GitHub
client/src/Components/local/organisms/setting-channels/channels/custom.tsx

Summary

Maintainability
F
3 days
Test Coverage
import { TableFooter } from '@material-ui/core'
import Paper from '@material-ui/core/Paper'
import Table from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody'
import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer'
import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow'
import { ForceData, TemplateBody } from 'src/custom-types'
import { ChannelCustom } from 'src/custom-types/channel-data'
import { ParticipantCustom, ParticipantTemplate } from 'src/custom-types/participant'
import cx from 'classnames'
import React, { useEffect, useState } from 'react'
import Confirm from '../../../atoms/confirm'
import FormGroup from '../../../atoms/form-group-shadow'
import EditableRow, { EDITABLE_SELECT_ITEM, Item as RowItem, Option } from '../../../molecules/editable-row'
import { SelectItem } from '../../../molecules/editable-row/types/props'
import { defaultParticipantCustom } from '../helpers/defaultParticipant'
import styles from '../styles.module.scss'
import { Role } from '../types/props'
import uniqId from 'uniqid'

type CustomChannelProps = {
  channel: ChannelCustom
  forces: ForceData[]
  messageTemplates: TemplateBody[]
  onChange: (channel: ChannelCustom) => void
}

export const CustomChannel: React.FC<CustomChannelProps> = ({
  channel,
  forces,
  messageTemplates,
  onChange
}) => {
  const [localChannelUpdates, setLocalChannelUpdates] = useState(channel)
  const [participantKey, confirmRemoveParticipant] = useState<number>(-1)
  const [postRemoveActionConfirmed, setPostRemoveActionConfirmed] = useState<boolean>(false)

  const messageTemplatesOptions: Array<Option> = messageTemplates.map(template => ({
    name: template.title,
    uniqid: template._id,
    value: template
  }))

  useEffect(() => {
    setLocalChannelUpdates(channel)
  }, [channel])

  useEffect(() => {
    onChange(localChannelUpdates)
  }, [localChannelUpdates])

  const renderContent = (): React.ReactNode => {
    if (!localChannelUpdates) return null

    const handleSaveRows = (participants: ParticipantCustom[]): void => {
      const newChannel = { ...localChannelUpdates }
      newChannel.participants = participants
      setLocalChannelUpdates(newChannel)
    }

    const rowToParticipantCustom = (forces: ForceData[], nextItems: RowItem[], participant: ParticipantCustom): ParticipantCustom => {
      const [force, access, templateOrPermission] = nextItems.filter(item => item.type === EDITABLE_SELECT_ITEM) as SelectItem[]
      const selectedForce = forces[force.active ? force.active[0] : 0]
      const roles: Array<Role['roleId']> = access.active ? access.active.map((key: number) => (selectedForce.roles[key].roleId)) : []
      const templates: ParticipantTemplate[] = templateOrPermission.active ? templateOrPermission.active.map((key: number) => {
        const { _id, title } = messageTemplatesOptions[key].value as TemplateBody
        return { _id, title }
      }) : []

      return {
        ...participant,
        subscriptionId: uniqId.time(),
        forceUniqid: selectedForce.uniqid,
        roles,
        templates
      }
    }

    const generateRowItemsCustom = (templatesOptions: Option[], forces: ForceData[], nextParticipant: ParticipantCustom): RowItem[] => {
      let forceSelected: number[] = [0]
      let roleOptions: Option[] = []
      const additionalFields: RowItem[] = []

      if (nextParticipant.forceUniqid) {
        const forceIndex = forces.findIndex(force => force.uniqid === nextParticipant.forceUniqid)
        if (forceIndex !== -1) {
          roleOptions = forces[forceIndex].roles.map((role): Option => ({
            name: role.name,
            uniqid: role.name,
            value: role
          }))
          forceSelected = [forceIndex]
        }
      }

      const partRoles: string[] = nextParticipant.roles
      const activeRoles: number[] = partRoles ? partRoles.map(role => {
        return roleOptions.findIndex(option => option.value.roleId === role)
      }).filter(active => active !== -1) : []

      let activeTemplates: number[] = []

      if (nextParticipant.templates && nextParticipant.templates.length) {
        activeTemplates = nextParticipant.templates.map(template => {
          return templatesOptions.findIndex(option => option.uniqid === template._id)
        }).filter(active => active !== -1)
      }

      additionalFields.push({
        active: activeTemplates,
        emptyTitle: 'Chat if empty',
        multiple: true,
        options: templatesOptions,
        uniqid: 'templates',
        type: EDITABLE_SELECT_ITEM
      })

      return [
        {
          active: forceSelected,
          multiple: false,
          options: forces,
          uniqid: 'forces',
          type: EDITABLE_SELECT_ITEM
        },
        {
          active: activeRoles,
          emptyTitle: 'All roles',
          multiple: true,
          options: roleOptions,
          uniqid: 'access',
          type: EDITABLE_SELECT_ITEM
        },
        ...additionalFields
      ]
    }

    const handleChangeRow = (nextItems: RowItem[], participant: ParticipantCustom): RowItem[] => {
      const nextParticipant = rowToParticipantCustom(forces, nextItems, participant)
      return generateRowItemsCustom(messageTemplatesOptions, forces, nextParticipant)
    }

    const handleCreateParticipant = (rowItems: RowItem[]): void => {
      if (localChannelUpdates) {
        handleSaveRows([
          ...localChannelUpdates.participants,
          rowToParticipantCustom(forces, rowItems, defaultParticipantCustom)
        ])
      } else {
        console.warn('Can`t create new participant, no current channel')
      }
    }

    const renderTableBody = (data: ChannelCustom): React.ReactElement[] => {
      if (!data.participants) return [<></>]
      return data.participants.map((participant, key) => {
        const handleSaveRow = (row: RowItem[], pKey = -1): void => {
          if (pKey === -1) {
            return
          }
          const nextParticipants = [...data.participants]
          nextParticipants[pKey] = rowToParticipantCustom(forces, row, participant)
          handleSaveRows(nextParticipants)
        }

        const handleRemoveParticipant = (): void => {
          const newItems = [...data.participants]
          newItems.splice(participantKey, 1)
          handleSaveRows(newItems)
        }

        if (postRemoveActionConfirmed && participantKey !== -1) {
          handleRemoveParticipant()
          setPostRemoveActionConfirmed(false)
          confirmRemoveParticipant(-1)
        }

        const items = generateRowItemsCustom(messageTemplatesOptions, forces, participant)
        return <EditableRow
          onRemove={(pKey = -1): void => confirmRemoveParticipant(pKey)}
          key={participant.subscriptionId}
          onChange={(nextItems: RowItem[]): RowItem[] => {
            return handleChangeRow(nextItems, participant)
          }}
          onSave={handleSaveRow}
          items={items}
          defaultMode='view'
          actions={true}
          participantKey={key}
          presentAsList
        />
      })
    }

    const renderTableFooter = (): React.ReactElement => {
      const items = generateRowItemsCustom(messageTemplatesOptions, forces, defaultParticipantCustom)
      return <EditableRow
        isGenerator={true}
        noSwitchOnReset
        onChange={(nextItems: RowItem[]): RowItem[] => {
          return handleChangeRow(nextItems, defaultParticipantCustom)
        }}
        onSave={handleCreateParticipant}
        items={items}
        defaultMode='edit'
        actions
      />
    }

    return (
      <div>
        <div className={styles.row}>
          <div className={cx(styles.col, styles.section, styles.table)}>
            <FormGroup placeholder="Participants and messages">
              <TableContainer component={Paper}>
                <Table aria-label="simple table">
                  <TableHead>
                    <TableRow>
                      <TableCell>Force</TableCell>
                      <TableCell align="left">Restrict access to specific roles</TableCell>
                      <TableCell align="left">Templates</TableCell>
                      <TableCell align="right">Actions</TableCell>
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    {renderTableBody(localChannelUpdates)}
                  </TableBody>
                  <TableFooter>
                    {renderTableFooter()}
                  </TableFooter>
                </Table>
              </TableContainer>
            </FormGroup>
          </div>
        </div>
      </div>
    )
  }

  return (
    <>
      <Confirm
        isOpen={participantKey !== -1}
        title="Delete Participation"
        message="Are you sure you want to permanently delete this participation?"
        cancelBtnText='Cancel'
        confirmBtnText='Delete'
        onCancel={(): void => confirmRemoveParticipant(-1)}
        onConfirm={(): void => setPostRemoveActionConfirmed(true)}
      />
      {renderContent()}
    </>
  )
}

export default CustomChannel