diff --git a/i18n/en.pot b/i18n/en.pot
index ff3103dea..df4b323cb 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-08-02T13:20:46.717Z\n"
-"PO-Revision-Date: 2023-08-02T13:20:46.717Z\n"
+"POT-Creation-Date: 2023-12-12T13:36:05.060Z\n"
+"PO-Revision-Date: 2023-12-12T13:36:05.060Z\n"
msgid "Yes"
msgstr "Yes"
@@ -56,43 +56,6 @@ msgstr "Error updating group"
msgid "Error creating group"
msgstr "Error creating group"
-msgid "Basic information"
-msgstr "Basic information"
-
-msgid "Name"
-msgstr "Name"
-
-msgid "Code"
-msgstr "Code"
-
-msgid "Used in analytics reports."
-msgstr "Used in analytics reports."
-
-msgid "User management"
-msgstr "User management"
-
-msgid "Add or remove users from this group."
-msgstr "Add or remove users from this group."
-
-msgid ""
-"To add a user to this group, go to the User section and edit the user group "
-"settings for a specific user."
-msgstr ""
-"To add a user to this group, go to the User section and edit the user group "
-"settings for a specific user."
-
-msgid "User group management"
-msgstr "User group management"
-
-msgid "This group can manage other user groups. Add managed user groups below."
-msgstr "This group can manage other user groups. Add managed user groups below."
-
-msgid "Available user groups"
-msgstr "Available user groups"
-
-msgid "Managed user groups"
-msgstr "Managed user groups"
-
msgid "Attributes"
msgstr "Attributes"
@@ -132,6 +95,12 @@ msgstr "Error updating role"
msgid "Error creating role"
msgstr "Error creating role"
+msgid "Basic information"
+msgstr "Basic information"
+
+msgid "Name"
+msgstr "Name"
+
msgid "Description"
msgstr "Description"
@@ -512,6 +481,9 @@ msgstr "Available user roles"
msgid "User roles this user is assigned"
msgstr "User roles this user is assigned"
+msgid "Available user groups"
+msgstr "Available user groups"
+
msgid "User groups this user is a member of"
msgstr "User groups this user is a member of"
@@ -530,8 +502,12 @@ msgstr "New password"
msgid "Password"
msgstr "Password"
-msgid "Minimum 8 characters, one uppercase and lowercase letter and one number"
-msgstr "Minimum 8 characters, one uppercase and lowercase letter and one number"
+msgid ""
+"Password should be at least 8 characters long, with at least one lowercase "
+"character, one uppercase character and one special character."
+msgstr ""
+"Password should be at least 8 characters long, with at least one lowercase "
+"character, one uppercase character and one special character."
msgid "Repeat new password"
msgstr "Repeat new password"
@@ -905,13 +881,6 @@ msgstr "Username for new user"
msgid "Password for new user"
msgstr "Password for new user"
-msgid ""
-"Password should be at least 8 characters long, with at least one lowercase "
-"character, one uppercase character and one special character."
-msgstr ""
-"Password should be at least 8 characters long, with at least one lowercase "
-"character, one uppercase character and one special character."
-
msgid "Cancel"
msgstr "Cancel"
diff --git a/src/components/GroupForm/BasicInformationSection.js b/src/components/GroupForm/BasicInformationSection.js
new file mode 100644
index 000000000..da9a08984
--- /dev/null
+++ b/src/components/GroupForm/BasicInformationSection.js
@@ -0,0 +1,53 @@
+import i18n from '@dhis2/d2-i18n'
+import {
+ composeValidators,
+ hasValue,
+ createMaxCharacterLength,
+} from '@dhis2/ui'
+import PropTypes from 'prop-types'
+import React from 'react'
+import { FormSection, TextField } from '../Form'
+import {
+ useDebouncedUniqueGroupNameValidator,
+ useDebouncedUniqueGroupCodeValidator,
+} from './validators'
+
+const codeLengthValidator = createMaxCharacterLength(50)
+
+const BasicInformationSection = React.memo(({ group }) => {
+ const debouncedUniqueGroupNameValidator =
+ useDebouncedUniqueGroupNameValidator({ groupName: group?.name })
+ const debouncedUniqueGroupCodeValidator =
+ useDebouncedUniqueGroupCodeValidator({ groupCode: group?.code })
+
+ return (
+
+
+
+
+ )
+})
+
+BasicInformationSection.propTypes = {
+ group: PropTypes.object,
+}
+
+export default BasicInformationSection
diff --git a/src/components/GroupForm/GroupForm.js b/src/components/GroupForm/GroupForm.js
index 2f1dcf8e5..674646379 100644
--- a/src/components/GroupForm/GroupForm.js
+++ b/src/components/GroupForm/GroupForm.js
@@ -1,52 +1,44 @@
import { useDataEngine } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
-import {
- NoticeBox,
- composeValidators,
- hasValue,
- createMaxCharacterLength,
- FinalForm,
-} from '@dhis2/ui'
+import { NoticeBox, FinalForm } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { useHistory } from 'react-router-dom'
import { useCurrentUser } from '../../hooks/useCurrentUser'
import Attributes from '../Attributes'
-import Form, { FormSection, TextField, TransferField } from '../Form'
-import { getGroupData } from './getGroupData'
+import Form, { FormSection } from '../Form'
+import BasicInformationSection from './BasicInformationSection.js'
+import {
+ createPostRequestBody,
+ createJsonPatchRequestBody,
+} from './createRequestBody.js'
import styles from './GroupForm.module.css'
import { useFormData } from './useFormData'
-import {
- useDebouncedUniqueGroupNameValidator,
- useDebouncedUniqueGroupCodeValidator,
-} from './validators'
-
-const codeLengthValidator = createMaxCharacterLength(50)
+import UserGroupManagementSection from './UserGroupManagementSection.js'
const GroupForm = ({ submitButtonLabel, group }) => {
const history = useHistory()
const engine = useDataEngine()
- const debouncedUniqueGroupNameValidator =
- useDebouncedUniqueGroupNameValidator({ engine, groupName: group?.name })
- const debouncedUniqueGroupCodeValidator =
- useDebouncedUniqueGroupCodeValidator({ engine, groupCode: group?.code })
const { loading, error, userGroupOptions, attributes } = useFormData()
const { currentUser, refreshCurrentUser } = useCurrentUser()
- const handleSubmit = async values => {
- const groupData = getGroupData({ values, group, attributes })
-
+ const handleSubmit = async (values, form) => {
try {
if (group) {
await engine.mutate({
- resource: `userGroups/${group.id}?mergeMode=MERGE`,
- type: 'update',
- data: groupData,
+ resource: 'userGroups',
+ id: group.id,
+ type: 'json-patch',
+ data: createJsonPatchRequestBody({
+ values,
+ attributes,
+ dirtyFields: form.getState().dirtyFields,
+ }),
})
} else {
await engine.mutate({
resource: 'userGroups',
type: 'create',
- data: groupData,
+ data: createPostRequestBody({ values, attributes }),
})
}
@@ -91,28 +83,7 @@ const GroupForm = ({ submitButtonLabel, group }) => {
{submitError.message}
)}
-
-
-
-
+
{
)}
-
- id) || []
- }
- />
-
+
{attributes.length > 0 && (
(
+
+ id) || []}
+ />
+
+))
+
+UserGroupManagementSection.propTypes = {
+ userGroupOptions: PropTypes.array.isRequired,
+ group: PropTypes.object,
+}
+
+export default UserGroupManagementSection
diff --git a/src/components/GroupForm/createRequestBody.js b/src/components/GroupForm/createRequestBody.js
new file mode 100644
index 000000000..bba73bc03
--- /dev/null
+++ b/src/components/GroupForm/createRequestBody.js
@@ -0,0 +1,74 @@
+import { getAttributeValues } from '../../attributes.js'
+
+const ATTRIBUTE_VALUES = 'attributeValues'
+
+export const createPostRequestBody = ({ values, attributes }) => {
+ const { name, code, members, managedGroups } = values
+
+ return {
+ name,
+ code,
+ users: members.additions.map(({ id }) => ({ id })),
+ managedGroups: managedGroups.map(id => ({ id })),
+ attributeValues: getAttributeValues({ attributes, values }),
+ }
+}
+
+export const createJsonPatchRequestBody = ({
+ values,
+ attributes,
+ dirtyFields,
+}) => {
+ const dirtyFieldsArray = Object.keys(dirtyFields).filter(
+ key => dirtyFields[key]
+ )
+ const patch = dirtyFieldsArray.reduce((acc, key) => {
+ if (key !== 'members' && !key.startsWith(ATTRIBUTE_VALUES)) {
+ const value =
+ key === 'managedGroups'
+ ? values[key].map(id => ({ id }))
+ : values[key]
+
+ acc.push({
+ op: 'replace',
+ path: '/' + key,
+ value: value ?? null,
+ })
+ }
+ return acc
+ }, [])
+
+ // Replace all attribute values if any were changed.
+ // There is no way to update individual attributes.
+ const anyDirtyAttributes = dirtyFieldsArray.some(key =>
+ key.startsWith(ATTRIBUTE_VALUES)
+ )
+
+ if (anyDirtyAttributes) {
+ patch.push({
+ op: 'replace',
+ path: `/${ATTRIBUTE_VALUES}`,
+ value: getAttributeValues({ attributes, values }),
+ })
+ }
+
+ if (dirtyFields.members) {
+ const { additions, removals } = values.members
+ for (const addition of additions) {
+ patch.push({
+ op: 'add',
+ path: '/users/-',
+ value: { id: addition.id },
+ })
+ }
+ for (const removal of removals) {
+ patch.push({
+ op: 'remove-by-id',
+ path: '/users',
+ id: removal.id,
+ })
+ }
+ }
+
+ return patch
+}
diff --git a/src/components/GroupForm/validators.js b/src/components/GroupForm/validators.js
index 245d833cf..11b299a5e 100644
--- a/src/components/GroupForm/validators.js
+++ b/src/components/GroupForm/validators.js
@@ -1,3 +1,4 @@
+import { useDataEngine } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import pDebounce from 'p-debounce'
import { useValidator } from '../../hooks/useValidator'
@@ -5,9 +6,9 @@ import { useValidator } from '../../hooks/useValidator'
const DEBOUNCE_DELAY_MS = 350
export const useDebouncedUniqueGroupNameValidator = ({
- engine,
groupName: currentGroupName,
}) => {
+ const engine = useDataEngine()
const findGroupByName = pDebounce(async groupName => {
const {
groups: { userGroups: groups },
@@ -33,6 +34,7 @@ export const useDebouncedUniqueGroupNameValidator = ({
return i18n.t('Name is already taken')
}
} catch (error) {
+ console.log(error)
return i18n.t(
'There was a problem whilst checking the availability of this group name'
)
@@ -42,9 +44,9 @@ export const useDebouncedUniqueGroupNameValidator = ({
}
export const useDebouncedUniqueGroupCodeValidator = ({
- engine,
groupCode: currentGroupCode,
}) => {
+ const engine = useDataEngine()
const findGroupByCode = pDebounce(async groupCode => {
const {
groups: { userGroups: groups },