-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
471 additions
and
151 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.form { | ||
background: var(--colors-white); | ||
padding: var(--spacers-dp16); | ||
padding-bottom: var(--spacers-dp32); | ||
} | ||
|
||
.formActions { | ||
position: fixed; | ||
left: 0; | ||
bottom: 0; | ||
width: 100vw; | ||
padding: var(--spacers-dp16); | ||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.8); | ||
background: var(--colors-white); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import i18n from '@dhis2/d2-i18n' | ||
import { NoticeBox } from '@dhis2/ui' | ||
import React, { useEffect, useRef } from 'react' | ||
import { useNavigate } from 'react-router-dom' | ||
import { getSectionPath } from '../../lib' | ||
import { ModelSection } from '../../types' | ||
import { StandardFormSection, StandardFormActions } from '../standardForm' | ||
import classes from './DefaultFormContents.module.css' | ||
|
||
export function DefaultFormContents({ | ||
children, | ||
section, | ||
submitError, | ||
submitting, | ||
}: { | ||
children: React.ReactNode | ||
section: ModelSection | ||
submitting: boolean | ||
submitError?: string | ||
}) { | ||
const formErrorRef = useRef<HTMLDivElement | null>(null) | ||
const navigate = useNavigate() | ||
|
||
const listPath = getSectionPath(section) | ||
useEffect(() => { | ||
if (submitError) { | ||
formErrorRef.current?.scrollIntoView({ behavior: 'smooth' }) | ||
} | ||
}, [submitError]) | ||
|
||
return ( | ||
<> | ||
<div className={classes.form}>{children}</div> | ||
{submitError && ( | ||
<StandardFormSection> | ||
<div ref={formErrorRef}> | ||
<NoticeBox | ||
error | ||
title={i18n.t( | ||
'Something went wrong when submitting the form' | ||
)} | ||
> | ||
{submitError} | ||
</NoticeBox> | ||
</div> | ||
</StandardFormSection> | ||
)} | ||
<div className={classes.formActions}> | ||
<StandardFormActions | ||
cancelLabel={i18n.t('Cancel')} | ||
submitLabel={i18n.t('Save and close')} | ||
submitting={submitting} | ||
onCancelClick={() => navigate(listPath)} | ||
/> | ||
</div> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import i18n from '@dhis2/d2-i18n' | ||
import { InputFieldFF, SingleSelectFieldFF, TextAreaFieldFF } from '@dhis2/ui' | ||
import * as React from 'react' | ||
import { Field as FieldRFF, useFormState } from 'react-final-form' | ||
import { StandardFormSection } from '../..' | ||
import { Attribute, AttributeValue } from '../../../types/generated' | ||
|
||
const inputWidth = '440px' | ||
|
||
type ValuesWithAttributes = { | ||
attributeValues: AttributeValue[] | ||
} | ||
|
||
type CustomAttributeProps = { | ||
attribute: Attribute | ||
index: number | ||
} | ||
|
||
function CustomAttribute({ attribute, index }: CustomAttributeProps) { | ||
const name = `attributeValues[${index}].value` | ||
const required = attribute.mandatory | ||
|
||
if (attribute.optionSet?.options) { | ||
const options = attribute.optionSet?.options.map( | ||
({ code, displayName }) => ({ | ||
value: code, | ||
label: displayName, | ||
}) | ||
) | ||
|
||
if (!required) { | ||
options.unshift({ value: '', label: i18n.t('<No value>') }) | ||
} | ||
|
||
return ( | ||
<StandardFormSection key={attribute.id}> | ||
<FieldRFF | ||
component={SingleSelectFieldFF} | ||
required={required} | ||
inputWidth={inputWidth} | ||
label={attribute.displayFormName} | ||
name={name} | ||
options={options} | ||
/> | ||
</StandardFormSection> | ||
) | ||
} | ||
|
||
if (attribute.valueType === 'TEXT') { | ||
return ( | ||
<StandardFormSection key={attribute.id}> | ||
<FieldRFF | ||
component={InputFieldFF} | ||
required={required} | ||
inputWidth={inputWidth} | ||
label={attribute.displayFormName} | ||
name={name} | ||
/> | ||
</StandardFormSection> | ||
) | ||
} | ||
|
||
if (attribute.valueType === 'LONG_TEXT') { | ||
return ( | ||
<StandardFormSection key={attribute.id}> | ||
<FieldRFF | ||
component={TextAreaFieldFF} | ||
required={required} | ||
inputWidth={inputWidth} | ||
label={attribute.displayFormName} | ||
name={name} | ||
/> | ||
</StandardFormSection> | ||
) | ||
} | ||
|
||
// @TODO: Verify that all value types have been covered! | ||
throw new Error(`Implement value type "${attribute.valueType}"!`) | ||
} | ||
|
||
export function CustomAttributes() { | ||
const formState = useFormState<ValuesWithAttributes>({ | ||
subscription: { initialValues: true }, | ||
}) | ||
|
||
const customAttributes = formState.initialValues.attributeValues?.map( | ||
(av) => av.attribute | ||
) | ||
|
||
return ( | ||
<> | ||
{customAttributes?.map((customAttribute, index) => { | ||
return ( | ||
<CustomAttribute | ||
key={customAttribute.id} | ||
attribute={customAttribute} | ||
index={index} | ||
/> | ||
) | ||
})} | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { CustomAttributes } from './CustomAttributes' |
44 changes: 44 additions & 0 deletions
44
src/components/form/attributes/useCustomAttributesQuery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { useDataQuery } from '@dhis2/app-runtime' | ||
import { useMemo } from 'react' | ||
import { useSchemaSectionHandleOrThrow } from '../../../lib' | ||
import { Attribute } from '../../../types/generated' | ||
|
||
const CUSTOM_ATTRIBUTES_QUERY = { | ||
attributes: { | ||
resource: 'attributes', | ||
params: ({ modelName }: Record<string, string>) => ({ | ||
fields: [ | ||
'id', | ||
'mandatory', | ||
'displayFormName', | ||
'valueType', | ||
'optionSet[options[id,displayName,name,code]]', | ||
], | ||
paging: false, | ||
filter: `${modelName}Attribute:eq:true`, | ||
}), | ||
}, | ||
} | ||
|
||
interface QueryResponse { | ||
attributes: { | ||
attributes: Attribute[] | ||
} | ||
} | ||
|
||
export function useCustomAttributesQuery() { | ||
const schemaSection = useSchemaSectionHandleOrThrow() | ||
|
||
const customAttributes = useDataQuery<QueryResponse>( | ||
CUSTOM_ATTRIBUTES_QUERY, | ||
{ variables: { modelName: schemaSection.name } } | ||
) | ||
|
||
return useMemo( | ||
() => ({ | ||
...customAttributes, | ||
data: customAttributes.data?.attributes.attributes || [], | ||
}), | ||
[customAttributes] | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
...s/formFields/DefaultIdentifibleFIelds.tsx → .../form/fields/DefaultIdentifibleFIelds.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './fields' | ||
export { DefaultFormContents } from './DefaultFormContents' | ||
export * from './attributes' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import get from 'lodash/fp/get' | ||
import { JsonPatchOperation } from '../../types' | ||
import { | ||
Attribute, | ||
AttributeValue, | ||
IdentifiableObject, | ||
} from './../../types/generated/models' | ||
|
||
type PatchAttribute = { | ||
id: Attribute['id'] | ||
} | ||
|
||
type PatchAttributeValue = { | ||
attribute: PatchAttribute | ||
value: AttributeValue['value'] | ||
} | ||
|
||
type ModelWithAttributeValues = IdentifiableObject & { | ||
attributeValues: PatchAttributeValue[] | ||
} | ||
|
||
interface FormatFormValuesArgs<FormValues extends ModelWithAttributeValues> { | ||
originalValue: unknown | ||
dirtyFields: { [key in keyof FormValues]?: boolean } | ||
values: FormValues | ||
} | ||
|
||
// these are removed from the dirtyKeys | ||
// attributeValues is an array in the form, thus the key will be attributeValues[0] etc | ||
// remove these, and replace with 'attributeValues' | ||
// style.code should post to style, not style.code, because it's a complex object | ||
const complexKeys = ['attributeValues', 'style'] as const | ||
export const sanitizeDirtyValueKeys = (dirtyKeys: string[]) => { | ||
const complexChanges = complexKeys.filter((complexKey) => | ||
dirtyKeys.some((dirtyKey) => dirtyKey.startsWith(complexKey)) | ||
) | ||
|
||
const dirtyKeysWithoutComplexKeys = dirtyKeys.filter( | ||
(dirtyKey) => | ||
!complexChanges.some((complexKey) => | ||
dirtyKey.startsWith(complexKey) | ||
) | ||
) | ||
|
||
return dirtyKeysWithoutComplexKeys.concat(complexChanges) | ||
} | ||
|
||
export function createJsonPatchOperations< | ||
FormValues extends ModelWithAttributeValues | ||
>({ | ||
dirtyFields, | ||
originalValue, | ||
values: unsanitizedValues, | ||
}: FormatFormValuesArgs<FormValues>): JsonPatchOperation[] { | ||
// Remove attribute values without a value | ||
const values = { | ||
...unsanitizedValues, | ||
attributeValues: unsanitizedValues.attributeValues | ||
.filter(({ value }) => !!value) | ||
.map((value) => ({ | ||
value: value.value, | ||
attribute: { id: value.attribute.id }, | ||
})), | ||
} | ||
|
||
const dirtyFieldsKeys = Object.keys(dirtyFields) | ||
const adjustedDirtyFieldsKeys = sanitizeDirtyValueKeys(dirtyFieldsKeys) | ||
|
||
return adjustedDirtyFieldsKeys.map((name) => ({ | ||
op: get(name, originalValue) ? 'replace' : 'add', | ||
path: `/${name.replace(/[.]/g, '/')}`, | ||
value: get(name, values) || '', | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.