All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hooks.submit.tsx Maven / Gradle / Ivy

import { NavigateFunction, useNavigate } from 'react-router';
import { FormikContextType } from 'formik';
import { FetchResult, useMutation } from '@apollo/client';
import { focusOnField } from 'components/form/utils/field-utils';
import { FormInfo } from 'contexts/FormInfoContext';
import Report from 'helpers/report';
import { TraitHelper } from 'helpers/traits';
import { useMountedRef } from 'hooks/mounted';
import { Notifier, useNotifier } from 'hooks/notification';
import { openTasksForm, StartProcessFunction } from 'hooks/process';
import { useSubmitting } from 'hooks/submitting';
import { useTranslator } from 'hooks/translator';
import { START_PROCESS_WITH_FORM } from 'queries/form';
import { SUBMIT_TASK } from 'queries/task';
import { ProcessDefinition, ProcessInstance, Task } from 'types/graphql';
import { SubmitValues } from 'types/types';
import { bannerQueries } from 'utils/banner-utils';
import { extractFiles, isEmptyValue } from 'utils/utils';

import { useFormInfo } from './form';

type LocalSubmitProps = {
  id: string
  isStartForm: boolean
}

export type FormikType = FormikContextType  //FormikSharedConfig<{}> //FormikProps 

export interface FormikFormInfo extends FormInfo {
  formik: FormikType
}

export type SubmitHandler = (values: any, formInfo: FormikFormInfo, local?: LocalSubmitProps) => void

export type SubmitReturn = { handleSubmit : SubmitHandler}

export type ProcessStarterKit = {
  startProcess: StartProcessFunction
  processDefinition: ProcessDefinition
}

export type TaskEndFunction = (tasks?: Task[] | Task) => void

export const useSubmitHandler = (id?: string, isStartForm?: boolean): SubmitReturn => {
  const navigate            = useNavigate()
  const notifier            = useNotifier()
  const {isMounted}         = useMountedRef()
  const {setSubmitting}     = useSubmitting()
  const taskEndHandler      = useTaskEndHandler()
  const { translator }      = useTranslator()
  const [doSubmitStartForm] = useMutation(START_PROCESS_WITH_FORM, { refetchQueries: bannerQueries })
  const [doSubmitTaskForm]  = useMutation(SUBMIT_TASK, { refetchQueries: bannerQueries })

  function handleSubmit(values: any, formInfo: FormikFormInfo, local?: LocalSubmitProps) {
    setSubmitting(true)
    const [rValues, files]  = extractFiles(values)
    const isLocalStartForm  = local != undefined ? local.isStartForm : isStartForm
    const localId           = local != undefined ? local.id : id
    const extractSubmitData = (result: FetchResult) => isLocalStartForm ? result?.data?.startProcess : result?.data?.submitTask
    const doSubmit          = isLocalStartForm ? doSubmitStartForm : doSubmitTaskForm

    if (isLocalStartForm == undefined || localId == undefined)
      notifier.error("The submit handler does not have enough process information to start the process")

    console.debug("Submit values: %o, files: %o", rValues, files)
    doSubmit({ variables: { id: localId, values: rValues, files } })
      .then(
        result => {
          const data = extractSubmitData(result)
          return handleSubmitResult(data, formInfo, taskEndHandler)
        },
        error => {
          const report = Report.from(error, translator, { category: Report.backend }, formInfo.processDefinition.key)
          report.addToNotifier(notifier)

          const specErrors = Report.toSpecErrors(error)
          if (specErrors.length > 0) {
            for (let i = 0; i < specErrors.length; i++) {
              // @ts-ignore
              const location = specErrors[i].extensions.location || ""
              const sp       = location.split(/\r?\n/).map((v: string, i: number) => i > 0  ? "          " + v : v).join("\r\n")
              console.error("Specification error\nMessage : %s\nLocation: %s", specErrors[i].message, sp)
            }
          } else {
            console.error("Submit error: %s", report.verboseMessage)
          }
        }
      )
      .finally(() => { if (isMounted()) setSubmitting(false) })
  }

  function handleSubmitResult(data: FetchResult & {__typename: string}, formInfo: FormikFormInfo, taskEndHandler: TaskEndFunction) {
    console.log("handleSubmitResult: data=%o, formInfo=%o", data, formInfo)
    switch (data.__typename) {
      case "ProcessInstanceEnded": taskEndHandler(); break 
      case "ProcessInstance":      handleProcessInstance(data as unknown as ProcessInstance, taskEndHandler);           break
      case "SubmitInProgress":     handleSubmitInProgress(formInfo, navigate);  break
      case "InputErrors":          handleInputErrors(data, formInfo, notifier); break
      default:                     alert("Submit result return an unexpected return type: " + data.__typename)
    }
  }

  function handleProcessInstance(instance: ProcessInstance, taskEndHandler: TaskEndFunction) {
    const tasks = instance?.assignedTasks
    taskEndHandler(tasks)
  }

  function handleSubmitInProgress(formInfo: FormInfo, navigate: NavigateFunction) {
    if (!formInfo?.statusUrl)
      notifier.error("Runtime issue: no status url available")
    else {
      const url = formInfo.statusUrl
      if (url) navigate(url)
    }
  }

  function handleInputErrors(data: any, formInfo: FormInfo & {formik: any}, notifier: Notifier) {
    const errors = Object.fromEntries(data.errors.map((error: any) => [error.path, error]))
    console.error("Input errors: %o", errors)

    if (!formInfo?.formik) {
      notifier.error("Input errors: " + JSON.stringify(errors))
    } else {
      formInfo.formik.setStatus(errors)

      data.errors.forEach((error: any) => formInfo.formik.setFieldError(error.path, error))

      Report.error(Report.input, Report.code.input.Error).addToNotifier(notifier)

      const first = data.errors[0]
      if (first && first.path) {
        focusOnField(first.path)
      }
    }
  }

  return { handleSubmit }
}

// go to the next task. eighter the only task that is given of the first task if the process has autostart.
export const useTaskEndHandler = (): TaskEndFunction => {
  const navigate          = useNavigate()
  const notifier          = useNotifier()
  const formInfo          = useFormInfo()
  const { translator }    = useTranslator()
  const processDefinition = formInfo.processDefinition

  return (tasks) => {
    const tsks: Task[] = Array.isArray(tasks) ? tasks : isEmptyValue(tasks) ? [] : [tasks!]
    if (tsks.length > 0)
      openTasksForm({ navigate, translator, notifier }, tsks)
    else if (TraitHelper.containsTrait(processDefinition?.traits, "autostart")) 
      navigate(`/gears/process/${processDefinition.key}`)
    else
      openTasksForm({ navigate, translator, notifier }, [])
  }
}






© 2015 - 2024 Weber Informatics LLC | Privacy Policy