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 },