Skip to content

Commit

Permalink
feat(de forms): handle submit error
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohammer5 committed Sep 12, 2023
1 parent 8f90415 commit 5386c3d
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 98 deletions.
13 changes: 8 additions & 5 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2023-09-12T13:53:30.779Z\n"
"PO-Revision-Date: 2023-09-12T13:53:30.779Z\n"
"POT-Creation-Date: 2023-09-12T14:52:51.124Z\n"
"PO-Revision-Date: 2023-09-12T14:52:51.124Z\n"

msgid "schemas"
msgstr "schemas"
Expand Down Expand Up @@ -549,6 +549,9 @@ msgstr "Positive or Zero Integer"
msgid "Tracker Associate"
msgstr "Tracker Associate"

msgid "Something went wrong when submitting the form"
msgstr "Something went wrong when submitting the form"

msgid "Cancel"
msgstr "Cancel"

Expand All @@ -570,12 +573,12 @@ msgstr "Last updated"
msgid "Public access"
msgstr "Public access"

msgid "Create data element"
msgstr "Create data element"

msgid "Exit without saving"
msgstr "Exit without saving"

msgid "Create data element"
msgstr "Create data element"

msgid "Basic information"
msgstr "Basic information"

Expand Down
88 changes: 68 additions & 20 deletions src/pages/dataElements/Edit.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useDataEngine, useDataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { FormApi } from 'final-form'
import React from 'react'
import { NoticeBox } from '@dhis2/ui'
import { FORM_ERROR, FormApi } from 'final-form'
import React, { useEffect, useRef } from 'react'
import { withTypes } from 'react-final-form'
import { useNavigate, useParams } from 'react-router-dom'
import { StandardFormActions } from '../../components'
import { StandardFormActions, StandardFormSection } from '../../components'
import { SCHEMA_SECTIONS } from '../../constants'
import { getSectionPath } from '../../lib'
import { JsonPatchOperation } from '../../types'
Expand Down Expand Up @@ -107,7 +108,7 @@ export const Component = () => {
return 'Loading...'
}

function onSubmit(values: FormValues, form: FinalFormFormApi) {
async function onSubmit(values: FormValues, form: FinalFormFormApi) {
const dirtyFields = form.getState().dirtyFields
const jsonPatchPayload = formatFormValues({
values,
Expand All @@ -126,9 +127,15 @@ export const Component = () => {
operations,
} as const

return dataEngine.mutate(ADD_NEW_DATA_ELEMENT_MUTATION, {
variables: { operations: jsonPatchPayload },
})
try {
await dataEngine.mutate(ADD_NEW_DATA_ELEMENT_MUTATION, {
variables: { operations: jsonPatchPayload },
})
} catch (e) {
return { [FORM_ERROR]: (e as Error | string).toString() }
}

navigate(listPath)
}

const initialValues = computeInitialValues({
Expand All @@ -138,22 +145,63 @@ export const Component = () => {

return (
<Form onSubmit={onSubmit} initialValues={initialValues}>
{({ handleSubmit, submitting }) => (
{({ handleSubmit, submitting, submitError }) => (
<form onSubmit={handleSubmit}>
<div className={classes.form}>
<DataElementFormFields />
</div>

<div className={classes.formActions}>
<StandardFormActions
cancelLabel={i18n.t('Cancel')}
submitLabel={i18n.t('Save and close')}
submitting={submitting}
onCancelClick={() => navigate(listPath)}
/>
</div>
<FormContents
submitError={submitError}
submitting={submitting}
/>
</form>
)}
</Form>
)
}

function FormContents({
submitError,
submitting,
}: {
submitting: boolean
submitError?: string
}) {
const formErrorRef = useRef<HTMLDivElement | null>(null)
const navigate = useNavigate()

useEffect(() => {
if (submitError) {
formErrorRef.current?.scrollIntoView({ behavior: 'smooth' })
}
}, [submitError])

return (
<>
{submitError && (
<StandardFormSection>
<div ref={formErrorRef}>
<NoticeBox
error
title={i18n.t(
'Something went wrong when submitting the form'
)}
>
{submitError}
</NoticeBox>
</div>
</StandardFormSection>
)}

<div className={classes.form}>
<DataElementFormFields />
</div>

<div className={classes.formActions}>
<StandardFormActions
cancelLabel={i18n.t('Cancel')}
submitLabel={i18n.t('Save and close')}
submitting={submitting}
onCancelClick={() => navigate(listPath)}
/>
</div>
</>
)
}
140 changes: 87 additions & 53 deletions src/pages/dataElements/New.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useDataEngine } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { Button, ButtonStrip, CircularLoader } from '@dhis2/ui'
import React from 'react'
import { NoticeBox } from '@dhis2/ui'
import { FORM_ERROR } from 'final-form'
import React, { useEffect, useRef } from 'react'
import { Form } from 'react-final-form'
import { useNavigate } from 'react-router-dom'
import { StandardFormActions, StandardFormSection } from '../../components'
Expand Down Expand Up @@ -33,9 +34,9 @@ function computeInitialValues(customAttributes: Attribute[]) {
formName: '',
valueType: '',
aggregationType: '',
categoryCombo: '',
optionSet: '',
commentOptionSet: '',
categoryCombo: { id: '' },
optionSet: { id: '' },
commentOptionSet: { id: '' },
legendSets: [],
aggregationLevels: [],
attributeValues,
Expand All @@ -48,37 +49,25 @@ const ADD_NEW_DATA_ELEMENT_MUTATION = {
data: (de: object) => de,
} as const

interface FormatFormValuesArgs {
values: FormValues
customAttributes: Attribute[]
}

function formatFormValues({ values, customAttributes }: FormatFormValuesArgs) {
function formatFormValues({ values }: { values: FormValues }) {
return {
aggregationLevels:
values.aggregationLevels?.map((level) => level) || [],
aggregationLevels: values.aggregationLevels,
aggregationType: values.aggregationType,
attributeValues: Object.entries(values.attributeValues || {}).map(
([attributeId, value]) => {
const customAttribute = customAttributes.find(
({ id }) => id === attributeId
) as Attribute
return {
value,
attribute: { id: attributeId, name: customAttribute.name },
}
}
),
categoryCombo: { id: values.categoryCombo },
attributeValues: values.attributeValues.filter(({ value }) => !!value),
categoryCombo: values.categoryCombo.id
? values.categoryCombo
: undefined,
code: values.code,
commentOptionSet: { id: values.commentOptionSet },
commentOptionSet: values.commentOptionSet.id
? values.commentOptionSet
: undefined,
description: values.description,
domainType: values.domainType,
fieldMask: values.fieldMask,
formName: values.formName,
legendSets: values.legendSet || [],
legendSets: values.legendSets,
name: values.name,
optionSet: { id: values.optionSet },
optionSet: values.optionSet.id ? values.optionSet : undefined,
shortName: values.shortName,
style: {
color: values.style?.color,
Expand Down Expand Up @@ -114,38 +103,83 @@ export const Component = () => {

const initialValues = computeInitialValues(customAttributesQuery.data)

function onSubmit(values: FormValues) {
const payload = formatFormValues({
values,
customAttributes: customAttributesQuery.data,
})

// We want the promise so we know when submitting is done. The promise
// returned by the mutation function of useDataMutation will never
// resolve
return dataEngine.mutate(ADD_NEW_DATA_ELEMENT_MUTATION, {
variables: payload,
})
async function onSubmit(values: FormValues) {
const payload = formatFormValues({ values })

try {
// We want the promise so we know when submitting is done. The promise
// returned by the mutation function of useDataMutation will never
// resolve
await dataEngine.mutate(ADD_NEW_DATA_ELEMENT_MUTATION, {
variables: payload,
})
} catch (e) {
console.log('> e', e)
return { [FORM_ERROR]: (e as Error | string).toString() }
}

navigate(listPath)
}

return (
<Form onSubmit={onSubmit} initialValues={initialValues}>
{({ handleSubmit, submitting }) => (
{({ handleSubmit, submitting, submitError }) => (
<form onSubmit={handleSubmit}>
<div className={classes.form}>
<DataElementFormFields />

<StandardFormSection>
<StandardFormActions
cancelLabel={i18n.t('Create data element')}
submitLabel={i18n.t('Exit without saving')}
submitting={submitting}
onCancelClick={() => navigate(listPath)}
/>
</StandardFormSection>
</div>
<FormContents
submitError={submitError}
submitting={submitting}
/>
</form>
)}
</Form>
)
}

function FormContents({
submitError,
submitting,
}: {
submitting: boolean
submitError?: string
}) {
const formErrorRef = useRef<HTMLDivElement | null>(null)
const navigate = useNavigate()

useEffect(() => {
if (submitError) {
formErrorRef.current?.scrollIntoView({ behavior: 'smooth' })
}
}, [submitError])

return (
<>
<div className={classes.form}>
<DataElementFormFields />

{submitError && (
<StandardFormSection>
<div ref={formErrorRef}>
<NoticeBox
error
title={i18n.t(
'Something went wrong when submitting the form'
)}
>
{submitError}
</NoticeBox>
</div>
</StandardFormSection>
)}

<StandardFormSection>
<StandardFormActions
cancelLabel={i18n.t('Exit without saving')}
submitLabel={i18n.t('Create data element')}
submitting={submitting}
onCancelClick={() => navigate(listPath)}
/>
</StandardFormSection>
</div>
</>
)
}
8 changes: 8 additions & 0 deletions src/pages/dataElements/form/customFields.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@
padding: var(--spacers-dp8) 0;
margin: 0;
}

label.domainTypeRadioButton {
display: inline-flex;
}

.domainTypeRadioButton + .domainTypeRadioButton {
margin-left: var(--spacers-dp8);
}
Loading

0 comments on commit 5386c3d

Please sign in to comment.