diff --git a/src/components/Form/Form.vue b/src/components/Form/Form.vue index 59302e6da..09211619a 100644 --- a/src/components/Form/Form.vue +++ b/src/components/Form/Form.vue @@ -78,7 +78,7 @@ diff --git a/src/managers/ContributorRoleManager/ContributorRoleManager.vue b/src/managers/ContributorRoleManager/ContributorRoleManager.vue new file mode 100644 index 000000000..12ca445c6 --- /dev/null +++ b/src/managers/ContributorRoleManager/ContributorRoleManager.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/managers/ContributorRoleManager/EditContributorRoleFormModal.vue b/src/managers/ContributorRoleManager/EditContributorRoleFormModal.vue new file mode 100644 index 000000000..10e642e4c --- /dev/null +++ b/src/managers/ContributorRoleManager/EditContributorRoleFormModal.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/managers/ContributorRoleManager/contributorRoleManagerStore.js b/src/managers/ContributorRoleManager/contributorRoleManagerStore.js new file mode 100644 index 000000000..adbcf44a7 --- /dev/null +++ b/src/managers/ContributorRoleManager/contributorRoleManagerStore.js @@ -0,0 +1,239 @@ +import {ref} from 'vue'; +import {defineComponentStore} from '@/utils/defineComponentStore'; +import {useModal} from '@/composables/useModal'; +import ContributorRoleDeleteDialogBody from '@/managers/ContributorRoleManager/ContributorRoleDeleteDialogBody.vue'; +import {useLocalize} from '@/composables/useLocalize'; +import {useUrl} from '@/composables/useUrl'; +import {useFetch} from '@/composables/useFetch'; +import EditcontributorRoleFormModal from '@/managers/ContributorRoleManager/EditContributorRoleFormModal.vue'; +import {useContributorRoleManagerAddRole} from '@/managers/ContributorRoleManager/useContributorRoleManagerFormAddRole.js'; +import {useForm} from '@/composables/useForm'; +import {cloneDeep} from 'lodash'; + +export const Actions = { + EDIT: 'roleEdit', + ADD: 'roleAdd', + DELETE: 'roleDelete', +}; +const {t} = useLocalize(); +export const useContributorRoleManagerStore = defineComponentStore( + 'contributorRoleManagerStore', + (props) => { + // Fetch selected roles + const {apiUrl: rolesApiUrl} = useUrl('contributorRoles'); + const {data: roles, fetch: fetchContributorRoles} = useFetch(rolesApiUrl, { + method: 'GET', + }); + fetchContributorRoles(); + + // Add/edit form + const {apiUrl: contributorRoleIdentifiersApiUrl} = useUrl( + 'contributorRoles/identifiers', + ); + const { + data: contributorRoleIdentifiers, + fetch: fetchContributorRoleIdentifiers, + } = useFetch(contributorRoleIdentifiersApiUrl, { + method: 'GET', + }); + + const contributorRoleForm = ref(null); + let resetContributorRoleForm = null; + setContributorRoleForm(); + + /** + * Fetch form data and set form + */ + async function setContributorRoleForm() { + await fetchContributorRoleIdentifiers(); + const {form, resetForm} = useContributorRoleManagerAddRole({ + rolesApiUrl, + contributorRoleIdentifiers, + }); + contributorRoleForm.value = form.value; + resetContributorRoleForm = resetForm; + } + + /** + * Get the form data for adding or editing a role. + * @param {string} action - Action being performed. One of Actions.ADD or Actions.EDIT. + * @param {Object|null} role - The role object. + * @return {Object} - The role form. + */ + function getContributorRoleForm(action, role) { + // Get a deep copy of the form to eliminate references + const preparedForm = cloneDeep(contributorRoleForm.value); + const {getField, setValues, setAction, setMethod} = useForm(preparedForm); + + role = role || {}; + + // return empty form if adding a new + if (action === Actions.ADD) { + return preparedForm; + } + + // Prepare form for editing + if (role.id && action === Actions.EDIT) { + // Disallow edit of identifier + const fieldIdentifier = getField('contributorRoleIdentifier'); + fieldIdentifier.options = [ + { + value: role.contributorRoleIdentifier, + label: role.contributorRoleIdentifier, + }, + ]; + + setValues({ + ...role, + }); + setMethod('PUT'); + setAction(`${preparedForm.action}/${role.id}`); + } + + return preparedForm; + } + + /*** + * Deletes a role. + * @param {Object} role + */ + function roleDelete(role) { + const {openDialog} = useModal(); + const {apiUrl} = useUrl(`contributorRoles/${role.id}`); + const {fetch: fetchDelete, isSuccess} = useFetch(apiUrl, { + method: 'DELETE', + }); + + openDialog({ + bodyComponent: ContributorRoleDeleteDialogBody, + bodyProps: { + onConfirm: async () => { + await fetchDelete(); + + if (isSuccess.value) { + openContributorRoleDeleteDialog(role.contributorRoleIdentifier); + await fetchContributorRoles(); + await resetContributorRoleForm?.(); + } + }, + contributorRoleIdentifier: role.contributorRoleIdentifier, + }, + title: t('manager.contributorRoles.alert.delete.confirmationTitle', { + contributorRoleIdentifier: role.contributorRoleIdentifier, + }), + modalStyle: 'negative', + }); + } + + /** + * Open a dialog indicating that the role was deleted. + */ + function openContributorRoleDeleteDialog(contributorRoleIdentifier) { + const {openDialog} = useModal(); + + openDialog({ + title: t('manager.contributorRoles.alert.deleted'), + message: t('manager.contributorRoles.alert.deleted.description', { + contributorRoleIdentifier: contributorRoleIdentifier, + }), + + actions: [ + { + label: t('manager.contributorRoles.backToRoles'), + callback: (close) => close(), + }, + ], + modalStyle: 'primary', + }); + } + + /** + * Open the form modal to add/edit role. + * @param {string} title - The title to display in the modal. + * @param {Object} form - The role form object to display in the modal. + */ + function openContributorRoleFormModal(title, form) { + const {openSideModal} = useModal(); + openSideModal(EditcontributorRoleFormModal, { + title, + formProps: form, + onSuccess: async () => { + await roleSaved(); + }, + }); + } + + /** + * Function to execute when a role is successfully saved via form. + */ + async function roleSaved() { + pkp.eventBus.$emit( + 'notify', + t('manager.contributorRoles.saved'), + 'success', + ); + closeContributorRoleFormModal(); + await fetchContributorRoles(); + await resetContributorRoleForm?.(); + } + + /** + * Close the currently opened role form modal. + */ + function closeContributorRoleFormModal() { + const {closeSideModal} = useModal(); + closeSideModal(EditcontributorRoleFormModal); + } + + /** + * Edit a role. + * @param {Object} role + */ + function roleEdit(role) { + openContributorRoleFormModal( + t('manager.contributorRoles.edit'), + getContributorRoleForm(Actions.EDIT, role), + ); + } + + /** + * Add a new role. + */ + function roleAdd() { + openContributorRoleFormModal( + t('manager.contributorRoles.add'), + getContributorRoleForm(Actions.ADD, null), + ); + } + + /** + * Get the actions available for a role item. + * @return {Array<{ label: string, icon: string, name: string, isWarnable?: boolean}>} - The list of actions available. + */ + function getItemActions() { + const actions = [ + { + label: t('common.edit'), + icon: 'Edit', + name: Actions.EDIT, + }, + { + label: t('manager.contributorRoles.delete.role'), + icon: 'Cancel', + name: Actions.DELETE, + isWarnable: true, + }, + ]; + + return actions; + } + + return { + getItemActions, + roleEdit, + roleAdd, + roleDelete, + roles, + }; + }, +); diff --git a/src/managers/ContributorRoleManager/useContributorRoleManagerFormAddRole.js b/src/managers/ContributorRoleManager/useContributorRoleManagerFormAddRole.js new file mode 100644 index 000000000..62fb4813c --- /dev/null +++ b/src/managers/ContributorRoleManager/useContributorRoleManagerFormAddRole.js @@ -0,0 +1,79 @@ +import {useForm} from '@/composables/useForm'; +import {useLocalize} from '@/composables/useLocalize'; +import {useApp} from '@/composables/useApp'; + +function setIdentifierOptions(options) { + return options.map((identifier) => ({ + value: identifier, + label: identifier, + })); +} + +export function useContributorRoleManagerAddRole({ + rolesApiUrl, + contributorRoleIdentifiers, +}) { + const {t} = useLocalize(); + + const { + form, + initEmptyForm, + addPage, + addGroup, + addFieldText, + addFieldSelect, + getField, + setValues, + } = useForm({}); + + const {getSupportedFormLocales} = useApp(); + + const supportedFormLocales = Object.keys(getSupportedFormLocales()); + const defaultNameValues = supportedFormLocales.reduce((val, l) => { + val[l] = ''; + return val; + }, {}); + + initEmptyForm('editContributorRole', { + action: rolesApiUrl.value, + }); + // Set visible locales + form.value.visibleLocales = supportedFormLocales; + + addPage('default', { + submitButton: {label: t('common.save')}, + }); + + addGroup('default'); + + addFieldSelect('contributorRoleIdentifier', { + label: t('manager.contributorRoles.identifier'), + isRequired: true, + options: setIdentifierOptions(contributorRoleIdentifiers.value ?? []), + value: contributorRoleIdentifiers.value?.[0], + }); + + addFieldText('name', { + label: t('manager.contributorRoles.name'), + description: t('manager.contributorRoles.name.description'), + isMultilingual: true, + isRequired: true, + value: defaultNameValues, + }); + + /** + * Reset form to defaults + */ + async function resetForm() { + const fieldIdentifier = getField('contributorRoleIdentifier'); + fieldIdentifier.options = setIdentifierOptions( + contributorRoleIdentifiers.value ?? [], + ); + setValues({ + contributorRoleIdentifier: contributorRoleIdentifiers.value?.[0], + name: defaultNameValues, + }); + } + + return {form, resetForm}; +} diff --git a/src/mocks/authors.js b/src/mocks/authors.js index c08ba9d33..466680063 100644 --- a/src/mocks/authors.js +++ b/src/mocks/authors.js @@ -22,11 +22,14 @@ export const dbarnes = { }, publicationId: 17, seq: 1, - userGroupId: 14, - userGroupName: { - fr_CA: '', - en: 'Author', - }, + contributorType: 'PERSON', + contributorRoles: [ + { + id: 1, + contributorRoleIdentifier: 'AUTHOR', + name: {en: 'Author', fr_CA: 'Auteur-e'}, + }, + ], }; export const lipsum = { @@ -54,11 +57,14 @@ export const lipsum = { }, publicationId: 17, seq: 1, - userGroupId: 14, - userGroupName: { - fr_CA: '', - en: 'Author', - }, + contributorType: 'PERSON', + contributorRoles: [ + { + id: 1, + contributorRoleIdentifier: 'AUTHOR', + name: {en: 'Author', fr_CA: 'Auteur-e'}, + }, + ], }; export default [{...dbarnes}, {...lipsum}]; diff --git a/src/mocks/contributor.js b/src/mocks/contributor.js index bdf84412d..9c6401b56 100644 --- a/src/mocks/contributor.js +++ b/src/mocks/contributor.js @@ -26,6 +26,12 @@ export default { preferredPublicName: {en: '', es: '', fr_CA: '', pt: ''}, publicationId: 10, seq: 0, - userGroupId: 14, - userGroupName: {en: 'Author', fr_CA: 'Auteur-e'}, + contributorType: 'PERSON', + contributorRoles: [ + { + id: 1, + contributorRoleIdentifier: 'AUTHOR', + name: {en: 'Author', fr_CA: 'Auteur-e'}, + }, + ], }; diff --git a/src/pages/dashboard/mocks/pageInitConfigEditorial.js b/src/pages/dashboard/mocks/pageInitConfigEditorial.js index 80d5030e7..ed281bc60 100644 --- a/src/pages/dashboard/mocks/pageInitConfigEditorial.js +++ b/src/pages/dashboard/mocks/pageInitConfigEditorial.js @@ -290,6 +290,21 @@ export default { method: 'POST', action: 'emit', fields: [ + { + name: 'contributorType', + component: 'field-options', + label: 'Contributor Type', + groupId: 'default', + isRequired: true, + isMultilingual: false, + isInert: false, + value: 'PERSON', + inputType: 'radio', + optIntoEdit: false, + optIntoEditLabel: '', + size: 'normal', + prefix: '', + }, { name: 'givenName', component: 'field-text', @@ -661,20 +676,20 @@ export default { authorId: 0, }, { - name: 'userGroupId', + name: 'contributorRoles', component: 'field-options', label: "Contributor's role", groupId: 'default', - isRequired: false, + isRequired: true, isMultilingual: false, isInert: false, - value: 14, - type: 'radio', + value: [1], + type: 'checkbox', isOrderable: false, allowOnlySorting: false, options: [ - {value: 14, label: 'Author'}, - {value: 15, label: 'Translator'}, + {value: 1, label: 'Author'}, + {value: 2, label: 'Translator'}, ], }, {