Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UIORGS-397: add privileged donor contacts list #590

Merged
merged 9 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Protection of viewing and changes of banking information by permissions. Refs UIORGS-388.
* Search organization on bank account number. Refs UIORGS-392.
* Enable "Hourly" and "Monthly" EDI export scheduling frequency options. Refs UIORGS-415.
* Create Privileged donor information accordion in organization record. Refs UIORGS-397.

## [5.0.0](https://github.com/folio-org/ui-organizations/tree/v5.0.0) (2023-10-12)
[Full Changelog](https://github.com/folio-org/ui-organizations/compare/v4.0.0...v5.0.0)
Expand Down
20 changes: 10 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@ class Organizations extends Component {
<Switch>
<Route
component={ContactsContainer}
path="/organizations/contacts/"
path={[
'/organizations/contacts/',
'/organizations/privileged-contacts/',
'/organizations/:orgId/contacts/',
'/organizations/:orgId/privileged-contacts/',
]}
/>
<Route
component={InterfaceContainer}
path="/organizations/interface/"
/>
<Route
component={ContactsContainer}
path="/organizations/:orgId/contacts/"
/>
<Route
component={InterfaceContainer}
path="/organizations/:orgId/interface/"
path={[
'/organizations/interface/',
'/organizations/:orgId/interface/',
]}
/>
<Route
path="/organizations/:orgId/integration/"
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"organizations-storage.emails": "1.1",
"organizations-storage.interfaces": "2.1",
"organizations-storage.phone-numbers": "2.0",
"organizations-storage.privileged-contacts": "1.0",
"organizations-storage.settings": "1.0",
"organizations-storage.urls": "1.1",
"tags": "1.0",
Expand Down Expand Up @@ -96,6 +97,8 @@
"organizations-storage.interfaces.collection.get",
"organizations-storage.phone-numbers.collection.get",
"organizations-storage.phone-numbers.item.get",
"organizations-storage.privileged-contacts.collection.get",
"organizations-storage.privileged-contacts.item.get",
"organizations-storage.settings.collection.get",
"organizations-storage.urls.collection.get",
"organizations-storage.urls.item.get",
Expand All @@ -118,6 +121,9 @@
"organizations-storage.contacts.item.post",
"organizations-storage.contacts.item.put",
"organizations-storage.contacts.item.delete",
"organizations-storage.privileged-contacts.item.post",
"organizations-storage.privileged-contacts.item.put",
"organizations-storage.privileged-contacts.item.delete",
"organizations-storage.interfaces.item.post",
"organizations-storage.interfaces.item.put",
"organizations-storage.interfaces.item.delete",
Expand Down
13 changes: 7 additions & 6 deletions src/Organizations/OrganizationDetails/OrganizationDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
import { NotesSmartAccordion } from '@folio/stripes/smart-components';
import {
handleKeyCommand,
PrivilegedDonorsListContainer,
TagsBadge,
TagsPane,
useAcqRestrictions,
Expand Down Expand Up @@ -68,7 +69,7 @@ const {
bankingInformationSection,
contactInformationSection,
contactPeopleSection,
donorContacts,
privilegedDonorInformation,
integrationDetailsSection,
interfacesSection,
notesSection,
Expand Down Expand Up @@ -341,12 +342,12 @@ const OrganizationDetails = ({
{
isDonorVisible && (
<Accordion
id={donorContacts}
label={ORGANIZATION_SECTION_LABELS[donorContacts]}
id={privilegedDonorInformation}
label={ORGANIZATION_SECTION_LABELS[privilegedDonorInformation]}
>
{/*
TODO: add Privileged donor information component https://issues.folio.org/browse/UIORGS-397
*/}
<PrivilegedDonorsListContainer
privilegedContactIds={organization.privilegedContacts}
/>
</Accordion>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import OrganizationDetails from './OrganizationDetails';
jest.mock('@folio/stripes-acq-components', () => ({
...jest.requireActual('@folio/stripes-acq-components'),
useAcqRestrictions: jest.fn().mockReturnValue({ restrictions: {} }),
PrivilegedDonorsListContainer: jest.fn(() => 'PrivilegedDonorsListContainer'),
TagsPane: jest.fn(() => 'TagsPane'),
}));
jest.mock('@folio/stripes-smart-components/lib/Notes/NotesSmartAccordion', () => () => 'NotesSmartAccordion');
Expand Down Expand Up @@ -137,7 +138,7 @@ describe('OrganizationDetails', () => {
},
});

expect(screen.getByText('ui-organizations.donorContacts')).toBeDefined();
expect(screen.getByText('ui-organizations.privilegedDonorInformation')).toBeDefined();
});

it('should display warning message if vendor has not unique account numbers', () => {
Expand Down
16 changes: 16 additions & 0 deletions src/Organizations/OrganizationForm/OrganizationForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { ViewMetaData } from '@folio/stripes/smart-components';
import {
FormFooter,
PrivilegedDonorContacts,
handleKeyCommand,
useAccordionToggle,
} from '@folio/stripes-acq-components';
Expand Down Expand Up @@ -55,6 +56,7 @@ const OrganizationForm = ({
[ORGANIZATION_SECTIONS.contactInformationSection]: false,
[ORGANIZATION_SECTIONS.contactPeopleSection]: false,
[ORGANIZATION_SECTIONS.interfacesSection]: false,
[ORGANIZATION_SECTIONS.privilegedDonorInformation]: false,
[ORGANIZATION_SECTIONS.vendorInformationSection]: false,
[ORGANIZATION_SECTIONS.bankingInformationSection]: false,
[ORGANIZATION_SECTIONS.vendorTermsSection]: false,
Expand Down Expand Up @@ -199,6 +201,20 @@ const OrganizationForm = ({
{
formValues.isVendor && (
<>
{
formValues.isDonor && (
<Accordion
id={ORGANIZATION_SECTIONS.privilegedDonorInformation}
label={ORGANIZATION_SECTION_LABELS[ORGANIZATION_SECTIONS.privilegedDonorInformation]}
>
<PrivilegedDonorContacts
orgId={id}
Copy link
Contributor

@usavkov-epam usavkov-epam Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

organizationId looks more clear to me, what do you think?

privilegedContactIds={formValues.privilegedContacts}
isPrivilegedContactEnabled
/>
</Accordion>
)
}
<Accordion
id={ORGANIZATION_SECTIONS.vendorInformationSection}
label={ORGANIZATION_SECTION_LABELS[ORGANIZATION_SECTIONS.vendorInformationSection]}
Expand Down
18 changes: 18 additions & 0 deletions src/Organizations/OrganizationForm/OrganizationForm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useHistory: jest.fn(),
}));
jest.mock('@folio/stripes-acq-components', () => ({
...jest.requireActual('@folio/stripes-acq-components'),
PrivilegedDonorContacts: jest.fn(() => 'PrivilegedDonorContacts'),
}));
jest.mock('@folio/stripes-components/lib/Commander', () => ({
HasCommand: jest.fn(({ children }) => <div>{children}</div>),
expandAllSections: jest.fn(),
Expand Down Expand Up @@ -188,6 +192,20 @@ describe('OrganizationForm', () => {

expect(screen.getByText('OrganizationBankingInfoForm')).toBeInTheDocument();
});

it('should render privileged donor contacts form', () => {
useBankingInformationSettings.mockReturnValue({ enabled: true });

renderOrganizationForm({
...defaultProps,
initialValues: {
isDonor: true,
isVendor: true,
},
});

expect(screen.getByText('PrivilegedDonorContacts')).toBeInTheDocument();
});
});

describe('Shortcuts', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/Organizations/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const ORGANIZATION_SECTIONS = {
notesSection: 'notesSection',
integrationDetailsSection: 'integrationDetailsSection',
agreements: 'linkedAgreements',
donorContacts: 'donorContacts',
privilegedDonorInformation: 'privilegedDonorInformation',
};

export const ORGANIZATION_SECTION_LABELS = {
Expand All @@ -26,7 +26,7 @@ export const ORGANIZATION_SECTION_LABELS = {
[ORGANIZATION_SECTIONS.accountsSection]: <FormattedMessage id="ui-organizations.accounts" />,
[ORGANIZATION_SECTIONS.integrationDetailsSection]: <FormattedMessage id="ui-organizations.integrationDetails" />,
[ORGANIZATION_SECTIONS.agreements]: <FormattedMessage id="ui-organizations.linkedAgreements.section" />,
[ORGANIZATION_SECTIONS.donorContacts]: <FormattedMessage id="ui-organizations.donorContacts" />,
[ORGANIZATION_SECTIONS.privilegedDonorInformation]: <FormattedMessage id="ui-organizations.privilegedDonorInformation" />,
};

export const CREATE_UNITS_PERM = 'organizations.acquisitions-units-assignments.assign';
Expand Down
1 change: 1 addition & 0 deletions src/common/constants/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const BANKING_INFORMATION_API = 'organizations/banking-information';
export const CATEGORIES_API = 'organizations-storage/categories';
export const CONTACTS_API = 'organizations-storage/contacts';
export const INTERFACES_API = 'organizations-storage/interfaces';
export const PRIVILEGED_CONTACTS_API = 'organizations-storage/privileged-contacts';
export const SETTINGS_API = 'organizations-storage/settings';
export const TYPES_API = 'organizations-storage/organization-types';

Expand Down
15 changes: 14 additions & 1 deletion src/common/resources/contacts.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { CONTACTS_API, MAX_LIMIT } from '../constants';
import {
CONTACTS_API,
MAX_LIMIT,
PRIVILEGED_CONTACTS_API,
} from '../constants';

export const contactResource = {
throwErrors: false,
Expand All @@ -9,6 +13,15 @@ export const contactResource = {
},
};

export const privilegedContactResource = {
throwErrors: false,
type: 'okapi',
path: `${PRIVILEGED_CONTACTS_API}/:{id}`,
POST: {
path: PRIVILEGED_CONTACTS_API,
},
};

export const baseContactsResource = {
throwErrors: false,
type: 'okapi',
Expand Down
8 changes: 5 additions & 3 deletions src/contacts/ContactsContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import {
} from '@folio/stripes-acq-components';

import { VIEW_ORG_DETAILS } from '../common/constants';
import ViewContact from './ViewContact';
import { PRIVILEGED_CONTACT_URL_PATH } from './constants';
import EditContact from './EditContact';
import ViewContact from './ViewContact';

function ContactsContainer({ history, match: { params, url } }) {
const sendCallout = useShowCallout();
const contactsUrlPath = url.includes(PRIVILEGED_CONTACT_URL_PATH) ? PRIVILEGED_CONTACT_URL_PATH : 'contacts';
const showMessage = useCallback((messageKey, messageType = 'success') => {
sendCallout({
type: messageType,
Expand All @@ -32,9 +34,9 @@ function ContactsContainer({ history, match: { params, url } }) {
if (!contactId) {
history.push(`${VIEW_ORG_DETAILS}${orgId}`);
} else {
history.push(`/organizations/${orgId}/contacts/details/${contactId}/view`);
history.push(`/organizations/${orgId}/${contactsUrlPath}/details/${contactId}/view`);
}
}, [history]);
}, [contactsUrlPath, history]);

const goToEdit = useCallback((props) => (
<EditContact
Expand Down
27 changes: 17 additions & 10 deletions src/contacts/EditContact/EditContactContainer.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import React, { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import { get } from 'lodash';
import PropTypes from 'prop-types';
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import ReactRouterPropTypes from 'react-router-prop-types';

import { Icon } from '@folio/stripes/components';
import { stripesConnect } from '@folio/stripes/core';

import { DICT_CATEGORIES } from '../../common/constants';
import { useTranslatedCategories } from '../../common/hooks';
import {
categoriesResource,
contactResource,
organizationResource,
privilegedContactResource,
} from '../../common/resources';
import { saveContact } from './util';
import EditContact from './EditContact';
import { getBackPath } from '../../common/utils/createItem';
import { DICT_CATEGORIES } from '../../common/constants';
import { useTranslatedCategories } from '../../common/hooks';
import { PRIVILEGED_CONTACT_URL_PATH } from '../constants';
import EditContact from './EditContact';
import { saveContact } from './util';

export const EditContactContainer = ({
history,
Expand All @@ -27,7 +30,9 @@ export const EditContactContainer = ({
showMessage,
}) => {
const isNew = !match.params.id;
const loadedContact = resources?.contact?.records?.[0];
const isPrivilegedContactUrl = match.path.includes(PRIVILEGED_CONTACT_URL_PATH);
const contactResources = isPrivilegedContactUrl ? 'privilegedContact' : 'contact';
const loadedContact = get(resources[contactResources], 'records[0]');
const categories = resources?.[DICT_CATEGORIES]?.records;
const [translatedCategories] = useTranslatedCategories(categories);
const contactsOrganization = resources?.contactsOrg?.records?.[0];
Expand All @@ -36,12 +41,12 @@ export const EditContactContainer = ({
if (onClose) {
onClose(orgId, contactId);
} else {
history.push(getBackPath(orgId, contactId, 'contacts'));
history.push(getBackPath(orgId, contactId, isPrivilegedContactUrl ? PRIVILEGED_CONTACT_URL_PATH : 'contacts'));
}
}, [match.params.id, onClose, history, orgId]);
}, [match.params.id, onClose, orgId, history, isPrivilegedContactUrl]);

const onSubmit = useCallback(contactValues => {
saveContact(mutator, contactValues, contactsOrganization)
saveContact(mutator, contactValues, contactsOrganization, isPrivilegedContactUrl)
.then(({ id }) => {
showMessage('ui-organizations.contacts.message.saved.success', 'success');
onCloseForm(id);
Expand All @@ -63,6 +68,7 @@ export const EditContactContainer = ({
const contact = isNew ? {} : loadedContact;
const { firstName, lastName } = contact;
const name = `${lastName}, ${firstName}`;

const paneTitle = isNew
? <FormattedMessage id="ui-organizations.contacts.create.paneTitle" />
: <FormattedMessage id="ui-organizations.contacts.edit.paneTitle" values={{ name }} />;
Expand All @@ -82,6 +88,7 @@ EditContactContainer.manifest = Object.freeze({
contact: contactResource,
[DICT_CATEGORIES]: categoriesResource,
contactsOrg: organizationResource,
privilegedContact: privilegedContactResource,
});

EditContactContainer.propTypes = {
Expand Down
35 changes: 35 additions & 0 deletions src/contacts/EditContact/EditContactContainer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DICT_CATEGORIES } from '../../common/constants';
import { saveContact } from './util';
import EditContact from './EditContact';
import { EditContactContainer } from './EditContactContainer';
import { PRIVILEGED_CONTACT_URL_PATH } from '../constants';

jest.mock('./util', () => ({
saveContact: jest.fn(),
Expand All @@ -29,6 +30,7 @@ const defaultProps = {
[DICT_CATEGORIES]: {
records: [{ value: 'Customer Service', id: 'f52ceea4-8e35' }],
},
privilegedContact: { records: [contact] },
},
showMessage: jest.fn(),
onClose: jest.fn(),
Expand Down Expand Up @@ -80,4 +82,37 @@ describe('EditContactContainer', () => {

expect(saveContact).toHaveBeenCalled();
});

describe('Privileged contact actions', () => {
const props = {
...defaultProps,
match: {
...defaultProps.match,
path: `/contacts/view/:id/${PRIVILEGED_CONTACT_URL_PATH}`,
},
onClose: undefined,
};

it('should save privileged contacts', async () => {
saveContact.mockClear().mockReturnValue(Promise.resolve());

renderEditContactContainer(props);

await screen.findByText('EditContact');

EditContact.mock.calls[0][0].onSubmit({});

expect(saveContact).toHaveBeenCalled();
});

it('should redirect to org details when form is closed', async () => {
renderEditContactContainer(props);

await screen.findByText('EditContact');

EditContact.mock.calls[0][0].onClose();

expect(historyMock.push).toHaveBeenCalledWith('/organizations/orgId/privileged-contacts/id/view');
});
});
});
Loading
Loading