diff --git a/i18n/en.pot b/i18n/en.pot
index 33f4e3df..16040710 100644
--- a/i18n/en.pot
+++ b/i18n/en.pot
@@ -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-07-31T11:36:19.094Z\n"
-"PO-Revision-Date: 2023-07-31T11:36:19.094Z\n"
+"POT-Creation-Date: 2023-08-03T09:45:13.203Z\n"
+"PO-Revision-Date: 2023-08-03T09:45:13.203Z\n"
msgid "schemas"
msgstr "schemas"
@@ -595,6 +595,15 @@ msgstr ""
msgid "Aggregation levels"
msgstr "Aggregation levels"
+msgid "Aggregation level(s)"
+msgstr "Aggregation level(s)"
+
+msgid "Custom attributes"
+msgstr "Custom attributes"
+
+msgid "Custom fields for your DHIS2 instance"
+msgstr "Custom fields for your DHIS2 instance"
+
msgid "Selected legends"
msgstr "Selected legends"
diff --git a/src/pages/dataElements/Edit.module.css b/src/pages/dataElements/Edit.module.css
new file mode 100644
index 00000000..de17044a
--- /dev/null
+++ b/src/pages/dataElements/Edit.module.css
@@ -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);
+}
diff --git a/src/pages/dataElements/Edit.tsx b/src/pages/dataElements/Edit.tsx
index 659183a6..8e0d41a8 100644
--- a/src/pages/dataElements/Edit.tsx
+++ b/src/pages/dataElements/Edit.tsx
@@ -1,10 +1,57 @@
+import { Button, ButtonStrip } from '@dhis2/ui'
import React from 'react'
-import { useParams } from 'react-router-dom'
+import { Form } from 'react-final-form'
+import { useNavigate } from 'react-router-dom'
+import classes from './Edit.module.css'
+import { DataElementForm } from './form'
+
+// @TODO(DataElements/edit): I suppose we want some of the initial values to be
+// dynamic? In that case, we'd have to load them and add loading/error UIs.
+const INITIAL_VALUES = {
+ legends: [],
+ domain: 'aggregate',
+ valueType: 'number',
+ aggregationType: 'sum',
+}
export const Component = () => {
- const { id } = useParams()
- // could do it like this instead of separate new and edit routes
- const isNew = id === 'new'
+ const navigate = useNavigate()
+ const onSubmit = (values: object) => {
+ console.log(
+ '@TODO(DataElements/edit): Implement onSubmit; values:',
+ values
+ )
+ }
+
+ return (
+
+ )}
+
+ )
}
diff --git a/src/pages/dataElements/New.module.css b/src/pages/dataElements/New.module.css
index 62d23d2d..77ac4df4 100644
--- a/src/pages/dataElements/New.module.css
+++ b/src/pages/dataElements/New.module.css
@@ -1,6 +1,7 @@
-.container {
- box-sizing: border-box;
- height: 100%;
+.form {
+ background: var(--colors-white);
+ padding: var(--spacers-dp16);
+ padding-bottom: var(--spacers-dp32);
}
.formActions {
diff --git a/src/pages/dataElements/New.tsx b/src/pages/dataElements/New.tsx
index 0193b162..37819e0a 100644
--- a/src/pages/dataElements/New.tsx
+++ b/src/pages/dataElements/New.tsx
@@ -2,19 +2,19 @@ import { Button, ButtonStrip } from '@dhis2/ui'
import React from 'react'
import { Form } from 'react-final-form'
import { useNavigate } from 'react-router-dom'
+import { StandardFormSection } from '../../components'
import { DataElementForm } from './form'
import classes from './New.module.css'
-// @TODO(DataElements/new): I suppose we want some of the initial values to be
-// dynamic? In that case, we'd have to load them and add loading/error UIs.
-const INITIAL_VALUES = {
- legends: [],
- domain: 'aggregate',
- valueType: 'number',
- aggregationType: 'sum',
-}
-
export const Component = () => {
+ // @TODO(DataElements/new): values dynamic or static?
+ const initialValues = {
+ legends: [],
+ domain: 'aggregate',
+ valueType: 'number',
+ aggregationType: 'sum',
+ }
+
const navigate = useNavigate()
const onSubmit = (values: object) => {
console.log(
@@ -26,29 +26,29 @@ export const Component = () => {
return (
)}
diff --git a/src/pages/dataElements/form/CustomAttributes.tsx b/src/pages/dataElements/form/CustomAttributes.tsx
new file mode 100644
index 00000000..32f5ecf6
--- /dev/null
+++ b/src/pages/dataElements/form/CustomAttributes.tsx
@@ -0,0 +1,67 @@
+import { InputFieldFF, TextAreaFieldFF } from '@dhis2/ui'
+import * as React from 'react'
+import { Field as FieldRFF } from 'react-final-form'
+import { StandardFormSection } from '../../../components'
+
+const inputWidth = '440px'
+
+export function CustomAttributes({
+ attributes = [],
+}: {
+ attributes?: Array<{
+ id: string,
+ displayFormName: string,
+ // @TODO(CustomAttributes): Implement all possible value types!
+ valueType: 'TEXT' | 'LONG_TEXT',
+ code: string,
+ mandatory: boolean,
+ }>,
+}) {
+ return (
+ <>
+ {attributes.map((attribute) => {
+ const {
+ mandatory: required,
+ valueType,
+ displayFormName,
+ id,
+ } = attribute
+
+ // @TODO(CustomAttributes): What to use as name?
+ const name = `attribute.${id}`
+
+ if (valueType === 'TEXT') {
+ return (
+
+
+
+ )
+ }
+
+ if (valueType === 'LONG_TEXT') {
+ return (
+
+
+
+ )
+ }
+
+ throw new Error(
+ `@TODO(CustomAttributes): Implement value type "${valueType}"!`
+ )
+ })}
+ >
+ )
+}
diff --git a/src/pages/dataElements/form/DataElementForm.module.css b/src/pages/dataElements/form/DataElementForm.module.css
deleted file mode 100644
index 3c5fef00..00000000
--- a/src/pages/dataElements/form/DataElementForm.module.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.container {
- background: var(--colors-white);
- padding: var(--spacers-dp16);
- padding: 16px;
- height: 100%;
- box-sizing: border-box;
-}
diff --git a/src/pages/dataElements/form/DataElementForm.stories.tsx b/src/pages/dataElements/form/DataElementForm.stories.tsx
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/pages/dataElements/form/DataElementForm.tsx b/src/pages/dataElements/form/DataElementForm.tsx
index 563c2571..fb3f9a54 100644
--- a/src/pages/dataElements/form/DataElementForm.tsx
+++ b/src/pages/dataElements/form/DataElementForm.tsx
@@ -2,6 +2,7 @@ import i18n from '@dhis2/d2-i18n'
import {
InputFieldFF,
SingleSelectFieldFF,
+ MultiSelectFieldFF,
TextAreaFieldFF,
} from '@dhis2/ui'
import React, { useState } from 'react'
@@ -17,270 +18,300 @@ import {
DomainField,
LegendsField,
} from './custom-fields'
-import classes from './DataElementForm.module.css'
+import { CustomAttributes } from './CustomAttributes'
import { EditableFieldWrapper } from './EditableFieldWrapper'
import {
- useCategoryCombosQuery,
- useOptionSetsQuery,
- useOptionSetCommentsQuery,
- useLegendsQuery,
- useAddLegendMutation,
+ useAddAggregationLevelMutation,
useAddCategoryComboMutation,
- useAddOptionSetMutation,
+ useAddLegendMutation,
useAddOptionSetCommentMutation,
+ useAddOptionSetMutation,
+ useAggregationLevelsQuery,
+ useCategoryCombosQuery,
+ useCustomAttributesQuery,
+ useLegendsQuery,
+ useOptionSetCommentsQuery,
+ useOptionSetsQuery,
} from './hooks'
export function DataElementForm() {
+ const customAttributes = useCustomAttributesQuery()
const categoryCombos = useCategoryCombosQuery()
const optionSets = useOptionSetsQuery()
const optionSetComments = useOptionSetCommentsQuery()
const legends = useLegendsQuery()
+ const aggregationLevels = useAggregationLevelsQuery()
const [addingLegend, setAddingLegend] = useState(false)
const [addingCategoryCombo, setAddingCategoryCombo] = useState(false)
const [addingOptionSet, setAddingOptionSet] = useState(false)
const [addingOptionSetComment, setAddingOptionSetComment] = useState(false)
+ const [addingAggregationLevel, setAddingAggregationLevel] = useState(false)
// @TODO(DataElementForm): Use these
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ /* eslint-disable @typescript-eslint/no-unused-vars */
const [addLegend] = useAddLegendMutation()
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [addCategoryCombo] = useAddCategoryComboMutation()
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [addOptionSet] = useAddOptionSetMutation()
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [addOptionSetComment] = useAddOptionSetCommentMutation()
+ const [addAggregationLevel] = useAddAggregationLevelMutation()
+ /* eslint-enable @typescript-eslint/no-unused-vars */
- if (
+ const loading = (
categoryCombos.loading ||
optionSets.loading ||
optionSetComments.loading ||
- legends.loading
- ) {
- return <>@TODO(DataElementForm): Loading>
- }
-
- if (
+ legends.loading ||
+ aggregationLevels.loading ||
+ customAttributes.loading
+ )
+ const error = (
categoryCombos.error ||
optionSets.error ||
optionSetComments.error ||
- legends.error
- ) {
- return <>@TODO(DataElementForm): Error>
+ legends.error ||
+ aggregationLevels.error ||
+ customAttributes.error
+ )
+
+ if (loading) {
+ return <>@TODO(DataElementForm): Loading>
+ }
+
+ if (error) {
+ return (
+ <>
+ @TODO(DataElementForm): Error
+ {error.toString()}
+ >
+ )
}
return (
<>
- {addingLegend && `@TODO(DataElementForm): add Modal(?) for adding a new legend`}
- {addingCategoryCombo && `@TODO(DataElementForm): add Modal(?) for adding a new category combo`}
- {addingOptionSet && `@TODO(DataElementForm): add Modal(?) for adding a new option set`}
- {addingOptionSetComment && `@TODO(DataElementForm): add Modal(?) for adding a new option set combo`}
+
+ {i18n.t('Basic information')}
+ {i18n.t('Set up the information for this data element')}
-
-
- {i18n.t('Basic information')}
- {i18n.t('Set up the information for this data element')}
+
+
+
-
- alert('@TODO(DataElementForm): Implement me! (13)')}
- />
-
+
+
+
-
- alert('@TODO(DataElementForm): Implement me! (14)')}
- />
-
+
+
+
-
- alert('@TODO(DataElementForm): Implement me! (15)')}
- />
-
+
+
+
-
-
-
+
+
+
-
- alert('@TODO(DataElementForm): Implement me! (17)')}
- />
-
+
+
+
-
-
-
+
+
+
-
- alert('@TODO(DataElementForm): Implement me! (18)')}
- />
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+ {i18n.t('Disaggregation and Option sets')}
+ {i18n.t('Set up disaggregation and predefined options.')}
+
+
+ setAddingCategoryCombo(true)}
+ >
alert('@TODO(DataElementForm): Implement me! (19)')}
+ label={i18n.t('Category combination (required)')}
+ helpText={i18n.t('Choose how this data element is disaggregated')}
+ options={categoryCombos.data}
/>
-
+
+
-
-
-
-
-
+
+ setAddingOptionSet(true)}
+ >
-
+
+
-
+
+ setAddingOptionSetComment(true)}
+ >
-
-
+
+
+
-
- {i18n.t('Disaggregation and Option sets')}
- {i18n.t('Set up disaggregation and predefined options.')}
+
+ {i18n.t('Legends')}
+ {i18n.t('Visualize values for this data element in Analytics app. Multiple legends can be applied.')}
-
- setAddingCategoryCombo(true)}
- >
-
-
-
+
+ setAddingLegend(true)}
+ />
+
+
-
- setAddingOptionSet(true)}
- >
-
-
-
+
+ {i18n.t('Aggregation levels')}
+
+ {`
+ @TODO(DataElementForm): Help text to describe the aggregation levels
+ functionality. It appears as if this section hasn't been
+ finalized yet by Joe, so I guess we'll have to talk about
+ this particluar part.
+ `}
+
-
- setAddingOptionSetComment(true)}
- >
-
-
-
-
+
+ setAddingAggregationLevel(true)}
+ >
+
+
+
+
+ {customAttributes.data?.length && (
- {i18n.t('Legends')}
- {i18n.t('Visualize values for this data element in Analytics app. Multiple legends can be applied.')}
+ {i18n.t('Custom attributes')}
+ {i18n.t('Custom fields for your DHIS2 instance')}
-
- setAddingLegend(true)}
- />
-
+
+ )}
-
- {i18n.t('Aggregation levels')}
-
- {`
- @TODO(DataElementForm): Help text to describe the aggregation levels
- functionality. It appears as if this section hasn't been
- finalized yet by Joe, so I guess we'll have to talk about
- this particluar part.
- `}
-
-
-
+ {addingLegend && `@TODO(DataElementForm): add Modal(?) for adding a new legend`}
+ {addingCategoryCombo && `@TODO(DataElementForm): add Modal(?) for adding a new category combo`}
+ {addingOptionSet && `@TODO(DataElementForm): add Modal(?) for adding a new option set`}
+ {addingOptionSetComment && `@TODO(DataElementForm): add Modal(?) for adding a new option set combo`}
+ {addingAggregationLevel && `@TODO(DataElementForm): add Modal(?) for adding a new aggregation level`}
>
)
}
diff --git a/src/pages/dataElements/form/hooks.ts b/src/pages/dataElements/form/hooks.ts
index 85625d2c..61ad19c8 100644
--- a/src/pages/dataElements/form/hooks.ts
+++ b/src/pages/dataElements/form/hooks.ts
@@ -1,3 +1,6 @@
+import { useDataQuery } from '@dhis2/app-runtime'
+import { useMemo } from 'react'
+
export function useCategoryCombosQuery() {
return {
loading: false,
@@ -65,3 +68,61 @@ export function useAddLegendMutation() {
data: null,
}]
}
+
+export function useAggregationLevelsQuery() {
+ return {
+ loading: false,
+ error: null,
+ data: [],
+ refetch: () => alert('@TODO(data element form hooks): Implement me!')
+ }
+}
+
+export function useAddAggregationLevelMutation() {
+ return [() => alert('@TODO(data element form hooks): Implement me!'), {
+ loading: false,
+ error: null,
+ data: null,
+ }]
+}
+
+const CUSTOM_ATTRIBUTES_QUERY = {
+ attributes: {
+ resource: 'attributes',
+ params: {
+ fields: ['*', 'optionSet[*]'],
+ paging: false,
+ filter: 'dataElementAttribute:eq:true',
+ },
+ },
+}
+
+// @TODO(useCustomAttributesQuery): create response type
+interface QueryResponse {
+ attributes: {
+ attributes: Array<{
+ id: string,
+ displayFormName: string,
+ // @TODO(CustomAttributes): Implement all possible value types!
+ valueType: 'TEXT' | 'LONG_TEXT',
+ code: string,
+ mandatory: boolean,
+ }>,
+ }
+}
+
+export function useCustomAttributesQuery() {
+ const customAttributes = useDataQuery(CUSTOM_ATTRIBUTES_QUERY)
+ console.log('> customAttributes', customAttributes)
+
+ const data = useMemo(() => {
+ return customAttributes.data?.attributes.attributes.map(attribute => {
+ return attribute
+ }) || []
+ }, [customAttributes.data])
+
+ return useMemo(() => ({
+ ...customAttributes,
+ data,
+ }), [customAttributes, data])
+}