diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b32063..88d8d40d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [UITEN-287](https://folio-org.atlassian.net/browse/UITEN-287) Add new permission to create, edit and remove reading room access in tenant settings. * [UITEN-277](https://issues.folio.org/browse/UITEN-277) Ensure Reading Room Access settings page is wrapped by `Title Manager`. * [UITEN-276](https://issues.folio.org/browse/UITEN-276) Reading Room Access (settings): Basic Layout. +* [UITEN-278] (https://issues.folio.org/browse/UITEN-278) Reading Room Access (settings): Create new reading room. ## [8.1.0](https://github.com/folio-org/ui-tenant-settings/tree/v8.1.0)(2024-03-19) [Full Changelog](https://github.com/folio-org/ui-tenant-settings/compare/v8.0.0...v8.1.0) diff --git a/package.json b/package.json index 5928570c..484d7ed7 100644 --- a/package.json +++ b/package.json @@ -214,6 +214,7 @@ "displayName": "Settings (tenant): Can view reading room access", "subPermissions": [ "reading-room.collection.get", + "inventory-storage.service-points.collection.get", "settings.tenant-settings.enabled" ], "visible": true @@ -242,8 +243,8 @@ "devDependencies": { "@babel/core": "^7.20.12", "@babel/eslint-parser": "^7.19.1", - "@babel/plugin-transform-class-properties": "^7.12.1", "@babel/plugin-proposal-decorators": "^7.0.0", + "@babel/plugin-transform-class-properties": "^7.12.1", "@babel/plugin-transform-private-methods": "^7.18.6", "@babel/plugin-transform-private-property-in-object": "^7.21.11", "@babel/plugin-transform-runtime": "^7.10.5", diff --git a/src/settings/ReadingRoomAccess/ReadingRoomAccess.js b/src/settings/ReadingRoomAccess/ReadingRoomAccess.js index 282d64fa..0ce12ac7 100644 --- a/src/settings/ReadingRoomAccess/ReadingRoomAccess.js +++ b/src/settings/ReadingRoomAccess/ReadingRoomAccess.js @@ -1,68 +1,18 @@ -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; +import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; +import _ from 'lodash'; import { TitleManager, useStripes } from '@folio/stripes/core'; -import { - Label, - Checkbox, -} from '@folio/stripes/components'; +import { Label } from '@folio/stripes/components'; import { ControlledVocab } from '@folio/stripes/smart-components'; -const readingRoomsData = { - values: { records: [ - { - 'id': 1, - 'name': 'RR1', - 'public': true, - 'servicePoint': [ - { - name: 'Circ Desk 1', - id: '3a40852d-49fd-4df2-a1f9-6e2641a6e91f', - }, - { - id: 'c4c90014-c8c9-4ade-8f24-b5e313319f4b', - name: 'Circ Desk 2' - }, - ], - // metadata: { - // 'createdDate': '2024-03-21T10:59:25.085+00:00', - // 'createdByUserId': 'af5ad81e-6857-5b65-9c0c-60942e56f872', - // 'updatedDate': '2024-03-21T10:59:25.085+00:00', - // 'updatedByUserId': 'af5ad81e-6857-5b65-9c0c-60942e56f872' - // } - }, - { - 'id': 2, - 'name': 'RR2', - 'public': true, - 'servicePoint': [{ - name: 'Circ Desk 1', - id: '3a40852d-49fd-4df2-a1f9-6e2641a6e91f', - }], - // metadata: { - // 'createdDate': '2024-03-21T10:59:25.085+00:00', - // 'createdByUserId': 'af5ad81e-6857-5b65-9c0c-60942e56f872', - // 'updatedDate': '2024-03-21T10:59:25.085+00:00', - // 'updatedByUserId': 'af5ad81e-6857-5b65-9c0c-60942e56f872' - // } - }, - ] }, - updaters: { - records: [] - }, - updaterIds: [], -}; +import { readingRoomAccessColumns } from './constant'; +import { getFormatter } from './getFormatter'; +import { getFieldComponents } from './getFieldComponents'; +import { getValidators } from './getValidators'; const hiddenFields = ['numberOfObjects', 'lastUpdated']; -const visibleFields = ['name', 'public', 'servicePoint']; -const formatter = { - 'public': (record) => , - 'servicePoint': (value) => { - const asp = value.servicePoint || []; - const items = asp.map(a =>
  • {a.name}
  • ); - return ; - } -}; const translations = { cannotDeleteTermHeader: 'ui-tenant-settings.settings.addresses.cannotDeleteTermHeader', cannotDeleteTermMessage: 'ui-tenant-settings.settings.addresses.cannotDeleteTermMessage', @@ -74,44 +24,113 @@ const translations = { const ReadingRoomAccess = (props) => { const intl = useIntl(); const stripes = useStripes(); + const { resources } = props; + + // service points defined in the tenant + const servicePoints = _.get(resources, ['RRAServicePoints', 'records', 0, 'servicepoints'], []); + /** + * A reading room can have more than one service points assigned to it. + * but a servicepoint cannot be mapped to more than one reading room + */ + const sps = []; + const rrs = _.get(resources, ['values', 'records']); + + rrs.forEach(rr => { + const asp = rr.servicePoints || []; + asp.forEach(s => { + if (!sps.includes(s.value)) { + sps.push(s.value); + } + }); + }); + + const options = servicePoints.reduce((acc, s) => { + if (!sps.includes(s.id) || s.name === 'None') { + acc.push({ value: s.id, label: s.name }); + } + return acc; + }, []); + + const fieldLabels = useMemo(() => ({ + [readingRoomAccessColumns.NAME]: intl.formatMessage({ id: 'ui-tenant-settings.settings.reading-room-access.name' }), + [readingRoomAccessColumns.ISPUBLIC]: intl.formatMessage({ id: 'ui-tenant-settings.settings.reading-room-access.public' }), + [readingRoomAccessColumns.SERVICEPOINTS]: intl.formatMessage({ id: 'ui-tenant-settings.settings.reading-room-access.asp' }), + }), [intl]); + + const visibleFields = useMemo(() => ([ + readingRoomAccessColumns.NAME, + readingRoomAccessColumns.ISPUBLIC, + readingRoomAccessColumns.SERVICEPOINTS, + ]), []); + + const getRequiredLabel = useCallback(columnLabel => ( + + ), []); - const columnMapping = { - name: ( - ), - public: intl.formatMessage({ id:'ui-tenant-settings.settings.reading-room-access.public' }), - servicePoint: intl.formatMessage({ id:'ui-tenant-settings.settings.reading-room-access.asp' }), - }; - const editable = false; // stripes.hasPerm('ui-users.settings.reading-room-access.all'); + return acc; + }, {}); + + return errors; + }, []); + + const validate = (item, index, items) => validateItem(item, items) || {}; + + const editable = stripes.hasPerm('ui-tenant-settings.settings.reading-room-access.all'); return ( true, + delete: () => true, + }} + formType="final-form" /> ); }; ReadingRoomAccess.manifest = Object.freeze({ + values: { + type: 'okapi', + records: 'readingRooms', + path: 'reading-room', + GET: { + path: 'reading-room?query=cql.allRecords=1 sortby name&limit=100' + } + }, + updaterIds: [], RRAServicePoints: { type: 'okapi', resource: 'service-points', @@ -119,4 +138,9 @@ ReadingRoomAccess.manifest = Object.freeze({ }, }); +ReadingRoomAccess.propTypes = { + resources: PropTypes.object, + mutator: PropTypes.object +}; + export default ReadingRoomAccess; diff --git a/src/settings/ReadingRoomAccess/ReadingRoomAccess.test.js b/src/settings/ReadingRoomAccess/ReadingRoomAccess.test.js index 177ce4c1..dd3d6344 100644 --- a/src/settings/ReadingRoomAccess/ReadingRoomAccess.test.js +++ b/src/settings/ReadingRoomAccess/ReadingRoomAccess.test.js @@ -1,45 +1,214 @@ -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { Form } from 'react-final-form'; import '../../../test/jest/__mocks__'; import buildStripes from '../../../test/jest/__new_mocks__/stripesCore.mock'; -import { - renderWithRouter, - renderWithReduxForm, -} from '../../../test/jest/helpers'; +import { renderWithRouter } from '../../../test/jest/helpers'; import ReadingRoomAccess from './ReadingRoomAccess'; const STRIPES = buildStripes(); -const renderReadingRoomAccess = (props) => { - const component = () => ( - - ); +const mutatorMock = { + values: { + // PUT: jest.fn(() => Promise.resolve()), + // DELETE: jest.fn(() => Promise.resolve()), + GET: jest.fn(() => Promise.resolve()), + POST: jest.fn(() => Promise.resolve()), + }, +}; - return renderWithRouter(renderWithReduxForm(component)); +const resourcesMock = { + values: { + dataKey: 'reading-room', + failed: false, + hasLoaded: true, + httpStatus: 200, + isPending: false, + module: '@folio/tenant-settings', + records: [ + { + 'id': '04efd73f-f7e3-4c19-8614-861941dd1d8e', + 'name': 'readingroom-422', + 'isPublic': true, + 'servicePoints': [ + { + 'value': '7c5abc9f-f3d7-4856-b8d7-6712462ca007', + 'label': 'Online' + } + ], + 'metadata': { + 'createdDate': '2024-04-22T11:41:52.904334', + 'createdByUserId': '2db30b15-7a36-4f02-9c77-999dbb470874' + } + }, + { + 'id': '75e44262-f68c-418c-ab4c-88c9198669c1', + 'name': 'reading-room-4221', + 'isPublic': true, + 'servicePoints': [ + { + 'value': '3a40852d-49fd-4df2-a1f9-6e2641a6e91f', + 'label': 'Circ Desk 1' + } + ], + 'metadata': { + 'createdDate': '2024-04-22T11:48:50.255863', + 'createdByUserId': '2db30b15-7a36-4f02-9c77-999dbb470874' + } + }, + { + 'id': '7c5abc9f-f3d7-4856-b8d7-6712462ca009', + 'name': 'reading-room-555', + 'isPublic': false, + 'servicePoints': [ + { + 'value': 'c4c90014-c8c9-4ade-8f24-b5e313319f4b', + 'label': 'Circ Desk 4' + } + ], + 'metadata': { + 'createdDate': '2024-04-19T11:56:15.192057', + 'createdByUserId': '2db30b15-7a36-4f02-9c77-999dbb470874', + 'updatedDate': '2024-04-22T04:38:00.75285', + 'updatedByUserId': '2db30b15-7a36-4f02-9c77-999dbb470874' + } + } + ], + }, + RRAServicePoints: { + 'hasLoaded': true, + 'isPending': false, + 'failed': false, + 'records': [ + { + 'servicepoints': [ + { + 'id': 'c4c90014-c8c9-4ade-8f24-b5e313319f4b', + 'name': 'Circ Desk 2', + 'code': 'cd2', + 'discoveryDisplayName': 'Circulation Desk -- Back Entrance', + 'pickupLocation': true, + 'holdShelfExpiryPeriod': { + 'duration': 5, + 'intervalId': 'Days' + }, + 'holdShelfClosedLibraryDateManagement': 'Keep_the_current_due_date', + 'staffSlips': [], + 'metadata': { + 'createdDate': '2024-04-23T01:53:59.590+00:00', + 'updatedDate': '2024-04-23T01:53:59.590+00:00' + } + }, + { + 'id': '3a40852d-49fd-4df2-a1f9-6e2641a6e91f', + 'name': 'Circ Desk 1', + 'code': 'cd1', + 'discoveryDisplayName': 'Circulation Desk -- Hallway', + 'pickupLocation': true, + 'holdShelfExpiryPeriod': { + 'duration': 3, + 'intervalId': 'Weeks' + }, + 'holdShelfClosedLibraryDateManagement': 'Keep_the_current_due_date', + 'staffSlips': [], + 'metadata': { + 'createdDate': '2024-04-23T01:53:59.598+00:00', + 'updatedDate': '2024-04-23T01:53:59.598+00:00' + } + }, + { + 'id': '7c5abc9f-f3d7-4856-b8d7-6712462ca007', + 'name': 'Online', + 'code': 'Online', + 'discoveryDisplayName': 'Online', + 'shelvingLagTime': 0, + 'pickupLocation': false, + 'holdShelfClosedLibraryDateManagement': 'Keep_the_current_due_date', + 'staffSlips': [], + 'metadata': { + 'createdDate': '2024-04-23T01:53:59.593+00:00', + 'updatedDate': '2024-04-23T01:53:59.593+00:00' + } + }, + { + 'id': '9d1b77e8-f02e-4b7f-b296-3f2042ddac54', + 'name': 'DCB', + 'code': '000', + 'discoveryDisplayName': 'DCB', + 'pickupLocation': true, + 'holdShelfExpiryPeriod': { + 'duration': 3, + 'intervalId': 'Days' + }, + 'holdShelfClosedLibraryDateManagement': 'Keep_the_current_due_date', + 'staffSlips': [], + 'metadata': { + 'createdDate': '2024-04-23T01:56:03.899+00:00', + 'updatedDate': '2024-04-23T01:56:03.899+00:00' + } + } + ], + 'totalRecords': 4 + } + ], + } }; +const renderReadingRoomAccess = (props) => renderWithRouter( +
    {}} + render={() => ( + + )} + /> +); + describe('Reading Room Access', () => { - describe('when no records exist', () => { - const props = { - resources : [], - stripes:{ STRIPES } - }; - beforeEach(() => { - renderReadingRoomAccess(props); - }); + const props = { + mutator: mutatorMock, + resources: resourcesMock, + stripes:{ STRIPES } + }; + + beforeEach(() => { + renderReadingRoomAccess(props); + }); + + it('should render a Pane with title "Reading room access"', () => { + expect(screen.getByLabelText('ui-tenant-settings.settings.reading-room-access.label')).toBeInTheDocument(); + }); + + it('should render new button', () => { + expect(screen.getByRole('button', { name: 'stripes-core.button.new' })).toBeVisible(); + }); + + it('should render correct result column', () => { + const columnHeaders = [ + /settings.reading-room-access.name/, + /settings.reading-room-access.public/, + /settings.reading-room-access.asp/ + ]; - it('should render a Pane with title "Reading room access"', () => { - expect(screen.getByLabelText('ui-tenant-settings.settings.reading-room-access.label')).toBeInTheDocument(); + columnHeaders.forEach((el) => expect(screen.getByRole('columnheader', { name: el })).toBeVisible()); + }); + + it('create reading room', async () => { + const newButton = screen.getByRole('button', { name: 'stripes-core.button.new' }); + userEvent.click(newButton); + await waitFor(() => { + expect(screen.getByText('stripes-core.button.save')).toBeInTheDocument(); + expect(document.querySelector('[name="items[0].name"]')).toBeInTheDocument(); }); - /** - * TODO: Uncomment this test once the mock data is removed from the js file, and pass the - * associated props. - */ - // it('should render text "There are no Reading room access"', () => { - // expect(screen.getByText('stripes-smart-components.cv.noExistingTerms')).toBeInTheDocument(); - // }); + userEvent.type(document.querySelector('[name="items[0].name"]'), 'test'); + userEvent.click(document.querySelectorAll("[class^='multiSelectToggleButton']")[0]); + userEvent.click(document.querySelectorAll('[class^="multiSelectOption"]')[0]); + + const saveButton = screen.getByRole('button', { name: 'stripes-core.button.save' }); + userEvent.click(saveButton); }); }); diff --git a/src/settings/ReadingRoomAccess/constant.js b/src/settings/ReadingRoomAccess/constant.js new file mode 100644 index 00000000..99b71752 --- /dev/null +++ b/src/settings/ReadingRoomAccess/constant.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line import/prefer-default-export +export const readingRoomAccessColumns = { + NAME: 'name', + ISPUBLIC: 'isPublic', + SERVICEPOINTS: 'servicePoints', +}; diff --git a/src/settings/ReadingRoomAccess/getFieldComponents.js b/src/settings/ReadingRoomAccess/getFieldComponents.js new file mode 100644 index 00000000..f9ad2049 --- /dev/null +++ b/src/settings/ReadingRoomAccess/getFieldComponents.js @@ -0,0 +1,56 @@ +import PropTypes from 'prop-types'; +import { Field } from 'react-final-form'; + +import { + Checkbox, + TextField, + MultiSelection, +} from '@folio/stripes/components'; + +import { readingRoomAccessColumns } from './constant'; + +/* eslint-disable import/prefer-default-export */ +export const getFieldComponents = (fieldLabels, options) => ({ + [readingRoomAccessColumns.NAME]: Object.assign( + ({ fieldProps, name, rowIndex, fieldIndex }) => ( + + ), + { propTypes: { fieldProps: PropTypes.object, name: PropTypes.string, rowIndex: PropTypes.number, fieldIndex: PropTypes.number } } + ), + [readingRoomAccessColumns.ISPUBLIC]: Object.assign( + ({ fieldProps, name, rowIndex }) => ( + + ), + { propTypes: { fieldProps: PropTypes.object, name: PropTypes.string, rowIndex: PropTypes.number } } + ), + [readingRoomAccessColumns.SERVICEPOINTS]: Object.assign( + ({ fieldProps, name, rowIndex }) => ( + e.preventDefault()} + /> + ), + { propTypes: { fieldProps: PropTypes.object, name: PropTypes.string, rowIndex: PropTypes.number } } + ) +}); diff --git a/src/settings/ReadingRoomAccess/getFormatter.js b/src/settings/ReadingRoomAccess/getFormatter.js new file mode 100644 index 00000000..baee1af3 --- /dev/null +++ b/src/settings/ReadingRoomAccess/getFormatter.js @@ -0,0 +1,25 @@ +import { Checkbox } from '@folio/stripes/components'; +import { readingRoomAccessColumns } from './constant'; + +/* eslint-disable import/prefer-default-export */ +export const getFormatter = ({ fieldLabels }) => ({ + [readingRoomAccessColumns.ISPUBLIC]: (record) => ( + + ), + [readingRoomAccessColumns.SERVICEPOINTS]: (record) => { + const asp = record.servicePoints || []; + const items = asp.map(a =>
  • {a.label}
  • ); + return ( +
      + {items} +
    + ); + } +}); diff --git a/src/settings/ReadingRoomAccess/getValidators.js b/src/settings/ReadingRoomAccess/getValidators.js new file mode 100644 index 00000000..5f8f0918 --- /dev/null +++ b/src/settings/ReadingRoomAccess/getValidators.js @@ -0,0 +1,23 @@ +/* eslint-disable import/prefer-default-export */ +import { FormattedMessage } from 'react-intl'; +import { readingRoomAccessColumns } from './constant'; + +const validators = { + [readingRoomAccessColumns.NAME]: (item) => { + const { name } = item; + if (!name) { + return ; + } + + return undefined; + }, + [readingRoomAccessColumns.SERVICEPOINTS]: (item) => { + const { servicePoints } = item; + if (!servicePoints) { + return ; + } + return undefined; + }, +}; + +export const getValidators = field => validators[field]; diff --git a/src/settings/index.js b/src/settings/index.js index c9eb8589..57875c2e 100644 --- a/src/settings/index.js +++ b/src/settings/index.js @@ -62,7 +62,7 @@ class Organization extends React.Component { route: 'reading-room', label: , component: ReadingRoomAccess, - // perm: 'ui-tenant-settings.settings.sso.view', # TO BE UPDATED + perm: 'ui-tenant-settings.settings.reading-room-access.view' }, { route: 'servicePoints',