diff --git a/CHANGELOG.md b/CHANGELOG.md index cc863c9e4..e42a36457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * Create new permission 'Users: Can view, and edit reading room access'. Refs UIU-3117. * Include DCB in 'User Type' search filter group. Refs UIU-3016. * Displaying Default Reading Room Access in User Records. Refs UIU-3114. +* Implement Reading Room Access functionality in user profile edit. Refs UIU-3115. ## [10.1.1](https://github.com/folio-org/ui-users/tree/v10.1.1) (2024-05-07) [Full Changelog](https://github.com/folio-org/ui-users/compare/v10.1.0...v10.1.1) diff --git a/package.json b/package.json index 51a2d25e7..1e3c73b14 100644 --- a/package.json +++ b/package.json @@ -1102,6 +1102,7 @@ "@folio/jest-config-stripes": "^2.0.0", "@folio/stripes": "^9.0.0", "@folio/stripes-cli": "^3.0.0", + "@folio/stripes-testing": "^4.4.0", "@formatjs/cli": "^6.1.3", "core-js": "^3.6.4", "eslint": "^7.32.0", diff --git a/src/components/EditSections/EditReadingRoomAccess/EditReadingRoomAccess.js b/src/components/EditSections/EditReadingRoomAccess/EditReadingRoomAccess.js new file mode 100644 index 000000000..08324a059 --- /dev/null +++ b/src/components/EditSections/EditReadingRoomAccess/EditReadingRoomAccess.js @@ -0,0 +1,70 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import { noop } from 'lodash'; + +import { + Accordion, + Badge, + Headline, + MultiColumnList, +} from '@folio/stripes/components'; + +import { rraColumns } from './constants'; +import { getFormatter } from './getFormatter'; + +const EditReadingRoomAccess = ({ + expanded, + onToggle, + accordionId, + form, + formData, +}) => { + const columnMapping = { + [rraColumns.ACCESS]: , + [rraColumns.READING_ROOM_NAME]: , + [rraColumns.NOTES]: , + }; + const visibleColumns = Object.keys(columnMapping); + const columnWidths = { + [rraColumns.ACCESS]: '15%', + [rraColumns.READING_ROOM_NAME]: '25%', + }; + + useEffect(() => { + const unregisterReadingRoomAccessList = form.registerField('readingRoomsAccessList', noop, { initialValue: [] }); + return () => { + unregisterReadingRoomAccessList(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + } + displayWhenClosed={{formData.length}} + > + + + ); +}; + +EditReadingRoomAccess.propTypes = { + expanded: PropTypes.bool, + onToggle: PropTypes.func, + accordionId: PropTypes.string.isRequired, + formData: PropTypes.object, + form: PropTypes.object, +}; + +export default EditReadingRoomAccess; diff --git a/src/components/EditSections/EditReadingRoomAccess/EditReadingRoomAccess.test.js b/src/components/EditSections/EditReadingRoomAccess/EditReadingRoomAccess.test.js new file mode 100644 index 000000000..461b9cd96 --- /dev/null +++ b/src/components/EditSections/EditReadingRoomAccess/EditReadingRoomAccess.test.js @@ -0,0 +1,152 @@ +import { screen, waitFor, act } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { within } from '@folio/jest-config-stripes/testing-library/dom'; +import { Form } from 'react-final-form'; + +import { runAxeTest } from '@folio/stripes-testing'; + +import renderWithRouter from 'helpers/renderWithRouter'; +import '../../../../test/jest/__mock__/matchMedia.mock'; + +import EditReadingRoomAccess from './EditReadingRoomAccess'; + +jest.unmock('@folio/stripes/components'); + +const unregisterFieldMock = jest.fn(); +const rraFieldStateMock = { + value: [ + { + 'id': '2205004b-ca51-4a14-87fd-938eefa8f5df', + 'userId': '2205005b-ca51-4a04-87fd-938eefa8f6de', + 'readingRoomId': 'ea7ac988-ede1-466b-968c-46a770333b14', + 'readingRoomName': 'rr-4', + 'access': 'ALLOWED', + 'notes': 'Allowed for this reading room...', + 'metadata': { + 'createdDate': '2024-05-15 18:39:31', + 'createdByUserId': '21457ab5-4635-4e56-906a-908f05e9233b', + 'updatedDate': '2024-05-15 18:40:27', + 'updatedByUserId': '21457ab5-4635-4e56-906a-908f05e9233b' + } + } + ] +}; +const onSubmit = jest.fn(); +const arrayMutators = { + concat: jest.fn(), + move: jest.fn(), + pop: jest.fn(), + push: jest.fn(), + remove: jest.fn(), + removeBatch: jest.fn(), + shift: jest.fn(), + swap: jest.fn(), + unshift: jest.fn(), + update: jest.fn() +}; +const renderEditReadingRoomAccess = (props, initialValues) => { + const component = () => ( + <> + + + ); + renderWithRouter( +
+ ); +}; +const props = { + expanded: true, + onToggle: jest.fn(), + accordionId: 'readingRoomAccess', + form: { + change: jest.fn(), + registerField: jest.fn().mockReturnValue(unregisterFieldMock), + getFieldState: jest.fn().mockReturnValue(rraFieldStateMock), + }, + formData: [ + { + 'id': '2205004b-ca51-4a14-87fd-938eefa8f5df', + 'userId': '2205005b-ca51-4a04-87fd-938eefa8f6de', + 'readingRoomId': 'ea7ac988-ede1-466b-968c-46a770333b14', + 'readingRoomName': 'rr-4', + 'access': 'ALLOWED', + 'notes': 'Allowed for this reading room...', + 'metadata': { + 'createdDate': '2024-05-15 18:39:31', + 'createdByUserId': '21457ab5-4635-4e56-906a-908f05e9233b', + 'updatedDate': '2024-05-15 18:40:27', + 'updatedByUserId': '21457ab5-4635-4e56-906a-908f05e9233b' + } + }, + { + 'id': 'fe1d83dc-e3f9-4e57-aa2c-0b245ae7eb19', + 'userId': '2205005b-ca51-4a04-87fd-938eefa8f6de', + 'readingRoomId': '754c6287-892c-4484-941a-23e050fc8888', + 'readingRoomName': 'abc', + 'access': 'NOT_ALLOWED', + 'notes': '', + 'metadata': { + 'createdDate': '2024-05-21 07:05:17', + 'createdByUserId': '21457ab5-4635-4e56-906a-908f05e9233b', + 'updatedDate': '2024-05-21 07:13:11', + 'updatedByUserId': '21457ab5-4635-4e56-906a-908f05e9233b' + } + } + ], +}; +describe('EditReadingRoomAccess', () => { + it('should render with no axe errors', async () => { + await runAxeTest({ + rootNode: document.body, + }); + }); + + it('should render component', () => { + renderEditReadingRoomAccess(props); + expect(screen.getByText('ui-users.readingRoom.readingRoomAccess')).toBeDefined(); + }); + + it('should display columns - access, name and note', () => { + renderEditReadingRoomAccess(props); + [ + 'ui-users.readingRoom.access', + 'ui-users.readingRoom.name', + 'ui-users.readingRoom.note', + ].forEach(col => expect(screen.getByText(col)).toBeDefined()); + }); + + it('should update the notes', async () => { + renderEditReadingRoomAccess(props); + const noteField1 = document.querySelectorAll('[id^=textarea]')[0]; + await act(async () => userEvent.type(noteField1, 'note1')); + await waitFor(() => expect(props.form.change).toHaveBeenCalled()); + }); + + it('should update access', async () => { + renderEditReadingRoomAccess(props); + const accessSelectField = document.querySelectorAll('[id=reading-room-access-select]')[1]; + await act(async () => userEvent.click(accessSelectField)); + const list = screen.getByRole('listbox'); + await act(async () => userEvent.click(within(list).getByText('ui-users.readingRoom.notAllowed', { exact: false }))); + await waitFor(() => expect(props.form.change).toHaveBeenCalled()); + }); + + it('should update both access and note', async () => { + renderEditReadingRoomAccess(props); + const noteField1 = document.querySelectorAll('[id^=textarea]')[0]; + await act(async () => userEvent.type(noteField1, 'note1')); + const accessSelectField = document.querySelectorAll('[id=reading-room-access-select]')[0]; + await act(async () => userEvent.click(accessSelectField)); + const list = screen.getByRole('listbox'); + await act(async () => userEvent.click(within(list).getByText('ui-users.readingRoom.allowed', { exact: false }))); + await waitFor(() => expect(props.form.change).toHaveBeenCalled()); + }); +}); diff --git a/src/components/EditSections/EditReadingRoomAccess/constants.js b/src/components/EditSections/EditReadingRoomAccess/constants.js new file mode 100644 index 000000000..a64b5eda0 --- /dev/null +++ b/src/components/EditSections/EditReadingRoomAccess/constants.js @@ -0,0 +1,18 @@ +import { FormattedMessage } from 'react-intl'; + +export const rraColumns = { + ACCESS: 'access', + READING_ROOM_NAME: 'readingRoomName', + NOTES: 'notes', +}; + +export const READING_ROOM_ACCESS_OPTIONS = [ + { + label: (), + value: 'ALLOWED' + }, + { + label: (), + value: 'NOT_ALLOWED' + } +]; diff --git a/src/components/EditSections/EditReadingRoomAccess/getFormatter.js b/src/components/EditSections/EditReadingRoomAccess/getFormatter.js new file mode 100644 index 000000000..2a41664ed --- /dev/null +++ b/src/components/EditSections/EditReadingRoomAccess/getFormatter.js @@ -0,0 +1,73 @@ +/* eslint-disable import/prefer-default-export */ +import PropTypes from 'prop-types'; +import { Field } from 'react-final-form'; +import { cloneDeep } from 'lodash'; +import { v4 as uuidv4 } from 'uuid'; + +import { Selection, TextArea } from '@folio/stripes/components'; + +import { rraColumns, READING_ROOM_ACCESS_OPTIONS } from './constants'; + +export const getFormatter = (form) => { + const updateRecord = (record, val, name, rowIndex) => { + const fieldState = form.getFieldState('readingRoomsAccessList'); + if (fieldState?.value && fieldState.value[rowIndex]) { + form.change(`readingRoomsAccessList[${rowIndex}][${name}]`, val); + } else { + const clonedRecord = cloneDeep(record); + clonedRecord[name] = val; + if (!clonedRecord.id) { + clonedRecord.id = uuidv4(); + } + form.change(`readingRoomsAccessList[${rowIndex}]`, clonedRecord); + } + }; + + return ({ + [rraColumns.ACCESS] : Object.assign( + ({ rowIndex, ...record }) => ( + { + return ( + { + updateRecord(record, val, input.name, rowIndex); + }} + /> + ); + }} + /> + ), + { rowIndex: PropTypes.number, record: PropTypes.object } + ), + [rraColumns.READING_ROOM_NAME] : ({ readingRoomName }) => readingRoomName, + [rraColumns.NOTES] : Object.assign( + ({ rowIndex, ...record }) => ( + ( +