From e603aa4cdb188f7f1bcb3a6fb37f5df3ec2853c3 Mon Sep 17 00:00:00 2001 From: SanttuA Date: Thu, 11 Apr 2024 13:46:47 +0300 Subject: [PATCH] Fixed a slot size selecting issue in reservation edit page (#314) Previously when admin selected to edit a reservation via manage reservations page using reservation page, it was possible to select time slot incorrectly. This happened because reservation page did not get correct slot size info from manage reservations page. This change fixes the issue by making sure slot size is given correctly when using reservation page to edit reservations. --- .../ManageReservationsPage.js | 41 +++++++--- .../__tests__/ManageReservationsPage.spec.js | 77 +++++++++++++++++-- .../manageReservationsPageSelector.spec.js | 10 ++- .../manageReservationsPageUtils.spec.js | 36 ++++++++- .../manageReservationsPageSelector.js | 4 +- .../manageReservationsPageUtils.js | 12 +++ 6 files changed, 160 insertions(+), 20 deletions(-) diff --git a/app/pages/manage-reservations/ManageReservationsPage.js b/app/pages/manage-reservations/ManageReservationsPage.js index 875ba45da..94c5dfc6b 100644 --- a/app/pages/manage-reservations/ManageReservationsPage.js +++ b/app/pages/manage-reservations/ManageReservationsPage.js @@ -34,7 +34,7 @@ import { denyPreliminaryReservation } from 'actions/reservationActions'; import manageReservationsPageSelector from './manageReservationsPageSelector'; -import { getFilteredReservations } from './manageReservationsPageUtils'; +import { getFilteredReservations, getResourceSlotSize } from './manageReservationsPageUtils'; import ReservationInfoModal from 'shared/modals/reservation-info'; import ReservationCancelModal from 'shared/modals/reservation-cancel'; import PageResultsText from './PageResultsText'; @@ -51,6 +51,8 @@ class ManageReservationsPage extends React.Component { showMassCancel: false, showConfirmCash: false, selectedReservation: null, + openingEditPage: false, + editPayload: {}, }; this.handleFetchReservations = this.handleFetchReservations.bind(this); @@ -65,6 +67,7 @@ class ManageReservationsPage extends React.Component { this.handleShowConfirmCash = this.handleShowConfirmCash.bind(this); this.handleHideConfirmCash = this.handleHideConfirmCash.bind(this); this.handleConfirmCash = this.handleConfirmCash.bind(this); + this.handleOpenEditPage = this.handleOpenEditPage.bind(this); } handleShowMassCancel() { @@ -93,6 +96,12 @@ class ManageReservationsPage extends React.Component { if (prevProps.location !== location) { this.handleFetchReservations(); } + if (prevProps.isFetchingResource && !this.props.isFetchingResource) { + const { openingEditPage } = this.state; + if (openingEditPage) { + this.handleOpenEditPage(); + } + } } onSearchFiltersChange(filters) { @@ -149,21 +158,33 @@ class ManageReservationsPage extends React.Component { actions.showReservationInfoModal(reservation); } - // opens reservation page in edit mode + // starts process of opening reservation edit page handleEditClick(reservation) { - const { history, actions } = this.props; - const normalizedReservation = Object.assign( {}, reservation, { resource: reservation.resource.id } ); + this.handleFetchResource(reservation.resource.id, reservation.begin); + const payload = { reservation: normalizedReservation }; + this.setState({ openingEditPage: true, editPayload: payload }); + } + + handleOpenEditPage() { + const { actions, resources, history } = this.props; + const { editPayload } = this.state; + + this.setState({ openingEditPage: false }); + // clear old selected reservations before selecting new reservation to edit actions.clearReservations(); - // fetch resource before changing page to make sure reservation page has - // all needed info to function - this.handleFetchResource(reservation.resource.id, reservation.begin); - actions.editReservation({ reservation: normalizedReservation }); - const nextUrl = `${getEditReservationUrl(normalizedReservation)}&path=manage-reservations`; + const payload = { ...editPayload }; + const slotSize = getResourceSlotSize(resources, payload.reservation.resource); + if (slotSize) { + payload.slotSize = slotSize; + } + + actions.editReservation(payload); + const nextUrl = `${getEditReservationUrl(editPayload.reservation)}&path=manage-reservations`; history.push(nextUrl); } @@ -315,6 +336,8 @@ ManageReservationsPage.propTypes = { units: PropTypes.array, reservations: PropTypes.array, reservationsTotalCount: PropTypes.number.isRequired, + resources: PropTypes.object, + isFetchingResource: PropTypes.bool, isFetchingReservations: PropTypes.bool, isFetchingUnits: PropTypes.bool, fontSize: PropTypes.string, diff --git a/app/pages/manage-reservations/__tests__/ManageReservationsPage.spec.js b/app/pages/manage-reservations/__tests__/ManageReservationsPage.spec.js index 85dd0dce5..02c6f1109 100644 --- a/app/pages/manage-reservations/__tests__/ManageReservationsPage.spec.js +++ b/app/pages/manage-reservations/__tests__/ManageReservationsPage.spec.js @@ -21,7 +21,7 @@ import MassCancelModal from '../../../shared/modals/reservation-mass-cancel/Mass import ConfirmCashModal from '../../../shared/modals/reservation-confirm-cash/ConfirmCashModal'; -describe('ManageReservationsFilters', () => { +describe('ManageReservationsPage', () => { const defaultProps = { actions: { clearReservations: jest.fn(), @@ -40,6 +40,8 @@ describe('ManageReservationsFilters', () => { locale: 'fi', units: [], reservations: [], + resources: {}, + isFetchingResource: false, reservationsTotalCount: 0, isFetchingReservations: false, isFetchingUnits: false, @@ -242,6 +244,12 @@ describe('ManageReservationsFilters', () => { }); describe('componentDidUpdate', () => { + const resource = { id: 'test-id' }; + const reservation = Reservation.build({ resource }); + const normalizedReservation = Object.assign( + {}, reservation, { resource: reservation.resource.id } + ); + test('calls handleFetchReservations when location changes', () => { const instance = getWrapper({ location: { search: '' } }).instance(); const spy = jest.spyOn(instance, 'handleFetchReservations'); @@ -256,6 +264,24 @@ describe('ManageReservationsFilters', () => { instance.componentDidUpdate({ location }); expect(spy).toHaveBeenCalledTimes(0); }); + + test('calls handleOpenEditPage when resource fetch completes and opening edit page', () => { + const instance = getWrapper({ isFetchingResource: false }).instance(); + instance.state.openingEditPage = true; + instance.state.editPayload = { reservation: normalizedReservation }; + const spy = jest.spyOn(instance, 'handleOpenEditPage'); + instance.componentDidUpdate({ isFetchingResource: true }); + expect(spy).toHaveBeenCalledTimes(1); + }); + + test('does not call handleOpenEditPage when not opening edit page', () => { + const instance = getWrapper({ isFetchingResource: false }).instance(); + instance.state.openingEditPage = false; + instance.state.editPayload = { reservation: normalizedReservation }; + const spy = jest.spyOn(instance, 'handleOpenEditPage'); + instance.componentDidUpdate({ isFetchingResource: true }); + expect(spy).toHaveBeenCalledTimes(0); + }); }); describe('onSearchFiltersChange', () => { @@ -343,11 +369,6 @@ describe('ManageReservationsFilters', () => { const normalizedReservation = Object.assign( {}, reservation, { resource: reservation.resource.id } ); - test('calls clearReservations', () => { - const instance = getWrapper().instance(); - instance.handleEditClick(reservation); - expect(defaultProps.actions.clearReservations.mock.calls.length).toBe(1); - }); test('calls handleFetchResource with correct params', () => { const instance = getWrapper().instance(); @@ -357,9 +378,48 @@ describe('ManageReservationsFilters', () => { expect(spy).toHaveBeenCalledWith(resource.id, reservation.begin); }); - test('calls editReservation with correct params', () => { + test('calls setState with correct params', () => { const instance = getWrapper().instance(); + const spy = jest.spyOn(instance, 'setState'); instance.handleEditClick(reservation); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0][0]).toStrictEqual({ + openingEditPage: true, + editPayload: { reservation: normalizedReservation } + }); + }); + }); + + describe('handleOpenEditPage', () => { + const resource = { id: 'test-id' }; + const reservation = Reservation.build({ resource }); + const normalizedReservation = Object.assign( + {}, reservation, { resource: reservation.resource.id } + ); + + test('calls setState with correct params', () => { + const instance = getWrapper().instance(); + instance.state.editPayload = { reservation: normalizedReservation }; + const spy = jest.spyOn(instance, 'setState'); + instance.handleOpenEditPage(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0][0]).toStrictEqual( + { openingEditPage: false } + ); + }); + + test('calls clearReservations', () => { + const instance = getWrapper().instance(); + instance.state.editPayload = { reservation: normalizedReservation }; + instance.handleOpenEditPage(); + expect(defaultProps.actions.clearReservations.mock.calls.length).toBe(1); + }); + + test('calls editReservation with correct params', () => { + const instance = getWrapper().instance(); + instance.state.editPayload = { reservation: normalizedReservation }; + instance.handleOpenEditPage(); + instance.state.editPayload = { reservation: normalizedReservation }; expect(defaultProps.actions.editReservation.mock.calls.length).toBe(1); expect(defaultProps.actions.editReservation.mock.calls[0][0]) .toStrictEqual({ reservation: normalizedReservation }); @@ -368,7 +428,8 @@ describe('ManageReservationsFilters', () => { test('calls history push with correct params', () => { const history = { push: jest.fn() }; const instance = getWrapper({ history }).instance(); - instance.handleEditClick(reservation); + instance.state.editPayload = { reservation: normalizedReservation }; + instance.handleOpenEditPage(); const expectedParam = `${getEditReservationUrl(normalizedReservation)}&path=manage-reservations`; expect(history.push.mock.calls.length).toBe(1); expect(history.push.mock.calls[0][0]).toBe(expectedParam); diff --git a/app/pages/manage-reservations/__tests__/manageReservationsPageSelector.spec.js b/app/pages/manage-reservations/__tests__/manageReservationsPageSelector.spec.js index 40ac45036..2d30a5456 100644 --- a/app/pages/manage-reservations/__tests__/manageReservationsPageSelector.spec.js +++ b/app/pages/manage-reservations/__tests__/manageReservationsPageSelector.spec.js @@ -10,7 +10,7 @@ jest.mock('state/selectors/dataSelectors', () => { }; }); -describe('/pages/favorites/favoritesPageSelector', () => { +describe('/pages/manage-reservations/manageReservationsPageSelector', () => { function getSelector() { const state = getState(); return manageReservationsPageSelector(state); @@ -48,6 +48,14 @@ describe('/pages/favorites/favoritesPageSelector', () => { expect(getSelector().reservationsTotalCount).toBeDefined(); }); + test('returns resources', () => { + expect(getSelector().resources).toBeDefined(); + }); + + test('returns isFetchingResource', () => { + expect(getSelector().isFetchingResource).toBeDefined(); + }); + test('returns fontSize', () => { expect(getSelector().fontSize).toBeDefined(); }); diff --git a/app/pages/manage-reservations/__tests__/manageReservationsPageUtils.spec.js b/app/pages/manage-reservations/__tests__/manageReservationsPageUtils.spec.js index 23c6548d1..f1ef6aaf9 100644 --- a/app/pages/manage-reservations/__tests__/manageReservationsPageUtils.spec.js +++ b/app/pages/manage-reservations/__tests__/manageReservationsPageUtils.spec.js @@ -1,5 +1,7 @@ import constants from 'constants/AppConstants'; -import { getFilteredReservations, getHiddenReservationCount, getPageResultsText } from '../manageReservationsPageUtils'; +import { + getFilteredReservations, getHiddenReservationCount, getPageResultsText, getResourceSlotSize +} from '../manageReservationsPageUtils'; import Reservation from 'utils/fixtures/Reservation'; import Resource from 'utils/fixtures/Resource'; @@ -111,4 +113,36 @@ describe('pages/manage-reservations/manageReservationsPageUtils', () => { }); }); }); + + describe('getResourceSlotSize', () => { + const resources = { + 1: { + id: 'one', + slotSize: '01:00:00' + }, + 2: { + id: 'two', + slotSize: '01:30:00' + } + }; + + test('returns slotSize of resource', () => { + const resourceId = '1'; + expect(getResourceSlotSize(resources, resourceId)).toBe('01:00:00'); + }); + + test('returns empty string if resource is not found', () => { + const resourceId = '3'; + expect(getResourceSlotSize(resources, resourceId)).toBe(''); + }); + + test('returns empty string if resources is not given', () => { + const resourceId = '1'; + expect(getResourceSlotSize(undefined, resourceId)).toBe(''); + }); + + test('returns empty string if resource id is not given', () => { + expect(getResourceSlotSize(resources, undefined)).toBe(''); + }); + }); }); diff --git a/app/pages/manage-reservations/manageReservationsPageSelector.js b/app/pages/manage-reservations/manageReservationsPageSelector.js index a118b2512..8ae7ac2fa 100644 --- a/app/pages/manage-reservations/manageReservationsPageSelector.js +++ b/app/pages/manage-reservations/manageReservationsPageSelector.js @@ -3,7 +3,7 @@ import { createSelector, createStructuredSelector } from 'reselect'; import ActionTypes from 'constants/ActionTypes'; import requestIsActiveSelectorFactory from 'state/selectors/factories/requestIsActiveSelectorFactory'; import { isAdminSelector } from 'state/selectors/authSelectors'; -import { userFavouriteResourcesSelector, unitsSelector } from 'state/selectors/dataSelectors'; +import { userFavouriteResourcesSelector, unitsSelector, resourcesSelector } from 'state/selectors/dataSelectors'; import { currentLanguageSelector } from 'state/selectors/translationSelectors'; import { fontSizeSelector } from '../../state/selectors/accessibilitySelectors'; @@ -31,6 +31,8 @@ const manageReservationsPageSelector = createStructuredSelector({ units: unitsArraySelector, reservations: reservationsSelector, reservationsTotalCount: reservationsCountSelector, + resources: resourcesSelector, + isFetchingResource: requestIsActiveSelectorFactory(ActionTypes.API.RESOURCE_GET_REQUEST), fontSize: fontSizeSelector, }); diff --git a/app/pages/manage-reservations/manageReservationsPageUtils.js b/app/pages/manage-reservations/manageReservationsPageUtils.js index 9188e01d6..6acb83563 100644 --- a/app/pages/manage-reservations/manageReservationsPageUtils.js +++ b/app/pages/manage-reservations/manageReservationsPageUtils.js @@ -62,3 +62,15 @@ export function getPageResultsText(currentPage, resultsPerPage, currentPageResul const to = resultsPerPage * currentPage + currentPageResults; return `${from} - ${to} / ${totalResults}`; } + +/** + * Gets slot size of resource + * @param {object} resources + * @param {string} resourceId + * @returns {string} slot size if resource is found in resources, empty string otherwise. + */ +export function getResourceSlotSize(resources, resourceId) { + const resource = !!resources && resourceId ? resources[resourceId] : undefined; + if (resource) return resource.slotSize; + return ''; +}