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

Added confirm cash modal into manage reservations page #297

Merged
merged 1 commit into from
Jan 22, 2024
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
2 changes: 2 additions & 0 deletions app/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
"common.yes": "Yes",
"SelectControl.noOptions": "No options",
"SelectControl.clearLabel": "Clear selection",
"ConfirmCashPaymentModal.title": "Cash payment approving",
"ConfirmCashPaymentModal.body": "Are you sure you want to approve the cash payment?",
"ConfirmReservationModal.beforeText": "Original reservation time:",
"ConfirmReservationModal.editTitle": "Changing reservation",
"ConfirmReservationModal.formInfo": "Please fill in the following data for your preliminary reservation. The fields marked with an asterisk (*) are mandatory.",
Expand Down
2 changes: 2 additions & 0 deletions app/i18n/messages/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
"common.yes": "Kyllä",
"SelectControl.noOptions": "Ei valintoja",
"SelectControl.clearLabel": "Poista valinta",
"ConfirmCashPaymentModal.title": "Käteismaksun hyväksyminen",
"ConfirmCashPaymentModal.body": "Haluatko varmasti hyväksyä käteismaksun?",
"ConfirmReservationModal.beforeText": "Alkuperäinen varausaika:",
"ConfirmReservationModal.editTitle": "Varauksen muuttaminen",
"ConfirmReservationModal.formInfo": "Täytä vielä seuraavat tiedot alustavaa varausta varten. Tähdellä (*) merkityt tiedot ovat pakollisia.",
Expand Down
2 changes: 2 additions & 0 deletions app/i18n/messages/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
"common.yes": "Ja",
"SelectControl.noOptions": "Inga val",
"SelectControl.clearLabel": "Ta bort val",
"ConfirmCashPaymentModal.title": "Godkännande av kontantbetalning",
"ConfirmCashPaymentModal.body": "Är du säker på att du vill godkänna kontantbetalning?",
"ConfirmReservationModal.beforeText": "Ursprunglig bokningstid",
"ConfirmReservationModal.editTitle": "Ändra bokningen",
"ConfirmReservationModal.formInfo": "Fyll ännu i följande uppgifter för den preliminära bokningen. Uppgifterna markerade med en asterisk (*) är obligatoriska.",
Expand Down
31 changes: 31 additions & 0 deletions app/pages/manage-reservations/ManageReservationsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import ReservationInfoModal from 'shared/modals/reservation-info';
import ReservationCancelModal from 'shared/modals/reservation-cancel';
import PageResultsText from './PageResultsText';
import MassCancelModal from '../../shared/modals/reservation-mass-cancel/MassCancelModal';
import ConfirmCashModal from '../../shared/modals/reservation-confirm-cash/ConfirmCashModal';


class ManageReservationsPage extends React.Component {
Expand All @@ -48,6 +49,8 @@ class ManageReservationsPage extends React.Component {
this.state = {
showOnlyFilters: [constants.RESERVATION_SHOWONLY_FILTERS.CAN_MODIFY],
showMassCancel: false,
showConfirmCash: false,
selectedReservation: null,
};

this.handleFetchReservations = this.handleFetchReservations.bind(this);
Expand All @@ -59,6 +62,9 @@ class ManageReservationsPage extends React.Component {
this.handleEditReservation = this.handleEditReservation.bind(this);
this.handleShowMassCancel = this.handleShowMassCancel.bind(this);
this.handleHideMassCancel = this.handleHideMassCancel.bind(this);
this.handleShowConfirmCash = this.handleShowConfirmCash.bind(this);
this.handleHideConfirmCash = this.handleHideConfirmCash.bind(this);
this.handleConfirmCash = this.handleConfirmCash.bind(this);
}

handleShowMassCancel() {
Expand All @@ -69,6 +75,14 @@ class ManageReservationsPage extends React.Component {
this.setState({ showMassCancel: false });
}

handleShowConfirmCash(reservation) {
this.setState({ showConfirmCash: true, selectedReservation: reservation });
}

handleHideConfirmCash() {
this.setState({ showConfirmCash: false, selectedReservation: null });
}

componentDidMount() {
this.props.actions.fetchUnits();
this.handleFetchReservations();
Expand Down Expand Up @@ -165,6 +179,9 @@ class ManageReservationsPage extends React.Component {
case constants.RESERVATION_STATE.CONFIRMED:
actions.confirmPreliminaryReservation(reservation);
break;
case constants.RESERVATION_STATE.WAITING_FOR_CASH_PAYMENT:
this.handleShowConfirmCash(reservation);
break;
case constants.RESERVATION_STATE.DENIED:
actions.denyPreliminaryReservation(reservation);
break;
Expand All @@ -173,6 +190,14 @@ class ManageReservationsPage extends React.Component {
}
}

// handles confirming cash payments via cash confirm modal onSubmit
handleConfirmCash() {
const { actions } = this.props;
const { selectedReservation } = this.state;
actions.confirmPreliminaryReservation(selectedReservation);
this.handleHideConfirmCash();
}

render() {
const {
t,
Expand All @@ -190,6 +215,7 @@ class ManageReservationsPage extends React.Component {
const {
showOnlyFilters,
showMassCancel,
showConfirmCash,
} = this.state;

const filters = getFiltersFromUrl(location, false);
Expand Down Expand Up @@ -270,6 +296,11 @@ class ManageReservationsPage extends React.Component {
onClose={this.handleHideMassCancel}
show={showMassCancel}
/>
<ConfirmCashModal
onClose={this.handleHideConfirmCash}
onSubmit={this.handleConfirmCash}
show={showConfirmCash}
/>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Reservation from 'utils/fixtures/Reservation';
import { getEditReservationUrl } from 'utils/reservationUtils';
import PageResultsText from '../PageResultsText';
import MassCancelModal from '../../../shared/modals/reservation-mass-cancel/MassCancelModal';
import ConfirmCashModal from '../../../shared/modals/reservation-confirm-cash/ConfirmCashModal';


describe('ManageReservationsFilters', () => {
Expand Down Expand Up @@ -164,6 +165,16 @@ describe('ManageReservationsFilters', () => {
expect(massCancelModal.prop('onClose')).toBe(instance.handleHideMassCancel);
expect(massCancelModal.prop('show')).toBe(instance.state.showMassCancel);
});

test('ConfirmCashModal', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const confirmCashModal = wrapper.find(ConfirmCashModal);
expect(confirmCashModal).toHaveLength(1);
expect(confirmCashModal.prop('onSubmit')).toBe(instance.handleConfirmCash);
expect(confirmCashModal.prop('onClose')).toBe(instance.handleHideConfirmCash);
expect(confirmCashModal.prop('show')).toBe(instance.state.showConfirmCash);
});
});

describe('functions', () => {
Expand All @@ -190,6 +201,31 @@ describe('ManageReservationsFilters', () => {
expect(spy.mock.calls[0][0]).toStrictEqual({ showMassCancel: false });
});

describe('handleShowConfirmCash', () => {
test('calls setState with correct params', () => {
const instance = getWrapper().instance();
const spy = jest.spyOn(instance, 'setState');
const reservation = { id: '123' };
instance.handleShowConfirmCash(reservation);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy.mock.calls[0][0]).toStrictEqual(
{ showConfirmCash: true, selectedReservation: reservation }
);
});
});

describe('handleHideConfirmCash', () => {
test('calls setState with correct params', () => {
const instance = getWrapper().instance();
const spy = jest.spyOn(instance, 'setState');
instance.handleHideConfirmCash();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy.mock.calls[0][0]).toStrictEqual(
{ showConfirmCash: false, selectedReservation: null }
);
});
});

describe('componentDidMount', () => {
test('calls fetchUnits', () => {
const instance = getWrapper().instance();
Expand Down Expand Up @@ -361,6 +397,15 @@ describe('ManageReservationsFilters', () => {
.toBe(reservation);
});

test('calls correct function when status is waiting for cash', () => {
const instance = getWrapper().instance();
const spy = jest.spyOn(instance, 'handleShowConfirmCash');
const status = constants.RESERVATION_STATE.WAITING_FOR_CASH_PAYMENT;
instance.handleEditReservation(reservation, status);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(reservation);
});

test('calls correct function when status is denied', () => {
const instance = getWrapper().instance();
const status = constants.RESERVATION_STATE.DENIED;
Expand All @@ -380,5 +425,26 @@ describe('ManageReservationsFilters', () => {
expect(defaultProps.actions.denyPreliminaryReservation.mock.calls.length).toBe(0);
});
});

describe('handleConfirmCash', () => {
test('calls confirmPreliminaryReservation with correct params', () => {
const reservation = Reservation.build();
const instance = getWrapper().instance();
instance.setState({ selectedReservation: reservation });
instance.handleConfirmCash();
expect(defaultProps.actions.confirmPreliminaryReservation.mock.calls.length).toBe(1);
expect(defaultProps.actions.confirmPreliminaryReservation.mock.calls[0][0])
.toBe(reservation);
});

test('sets correct state values', () => {
const reservation = Reservation.build();
const instance = getWrapper().instance();
instance.setState({ selectedReservation: reservation, showConfirmCash: true });
instance.handleConfirmCash();
expect(instance.state.showConfirmCash).toBe(false);
expect(instance.state.selectedReservation).toBe(null);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ function ManageReservationsDropdown({
)}
{userCanModify && isWaitingForCashPayment && (
<MenuItem
onClick={() => onEditReservation(reservation, reservationStates.CONFIRMED)}
onClick={
() => onEditReservation(reservation, reservationStates.WAITING_FOR_CASH_PAYMENT)}
>
{t('common.confirmCashPayment')}
</MenuItem>
Expand Down
1 change: 1 addition & 0 deletions app/shared/modals/_modals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import './reservation-success/reservation-success-modal';
@import './reservation-payment/reservation-payment-modal';
@import './reservation-mass-cancel/reservation-mass-cancel-modal';
@import './reservation-confirm-cash/reservation-confirm-cash-modal';

.modal-body {
overflow-y: scroll;
Expand Down
64 changes: 64 additions & 0 deletions app/shared/modals/reservation-confirm-cash/ConfirmCashModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-bootstrap/lib/Modal';
import Button from 'react-bootstrap/lib/Button';
import { connect } from 'react-redux';

import injectT from '../../../i18n/injectT';
import { currentLanguageSelector } from '../../../state/selectors/translationSelectors';
import { fontSizeSelector } from '../../../state/selectors/accessibilitySelectors';

function ConfirmCashModal({
show, onClose, t, onSubmit, fontSize
}) {
return (
<Modal
animation={false}
className={fontSize}
onHide={() => onClose()}
show={show}
>
<Modal.Header closeButton closeLabel={t('ModalHeader.closeButtonText')}>
<Modal.Title componentClass="h2">{t('ConfirmCashPaymentModal.title')}</Modal.Title>
</Modal.Header>
<Modal.Body>
{t('ConfirmCashPaymentModal.body')}
</Modal.Body>
<Modal.Footer>
<Button
bsStyle="primary"
className={fontSize}
onClick={() => onClose()}
>
{t('common.back')}
</Button>
<Button
bsStyle="success"
className={fontSize}
onClick={() => onSubmit()}
>
{t('common.confirmCashPayment')}
</Button>
</Modal.Footer>
</Modal>
);
}

ConfirmCashModal.propTypes = {
onClose: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
fontSize: PropTypes.string.isRequired,
};

function mapStateToProps(state) {
return {
currentLanguage: currentLanguageSelector(state),
fontSize: fontSizeSelector(state),
};
}

const UnconnectedConfirmCashModal = injectT(ConfirmCashModal);
export { UnconnectedConfirmCashModal };
export default connect(mapStateToProps, null)(injectT(ConfirmCashModal));
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import Button from 'react-bootstrap/lib/Button';
import Modal from 'react-bootstrap/lib/Modal';

import { shallowWithIntl } from 'utils/testUtils';
import {
UnconnectedConfirmCashModal as ConfirmCashModal,
} from './ConfirmCashModal';

describe('shared/modals/reservation-confirm-cash/ConfirmCashModal', () => {
const defaultProps = {
onClose: () => {},
onSubmit: () => {},
fontSize: 'test-large',
show: true,
};

function getWrapper(extraProps = {}) {
return shallowWithIntl(<ConfirmCashModal {...defaultProps} {...extraProps} />);
}

describe('render', () => {
test('renders a Modal component', () => {
const modalComponent = getWrapper().find(Modal);
expect(modalComponent.length).toBe(1);
expect(modalComponent.prop('animation')).toBe(false);
expect(modalComponent.prop('className')).toBe(defaultProps.fontSize);
expect(modalComponent.prop('onHide')).toBeDefined();
expect(modalComponent.prop('show')).toBe(defaultProps.show);
});

describe('Modal header', () => {
function getModalHeaderWrapper(props) {
return getWrapper(props).find(Modal.Header);
}

test('is rendered', () => {
expect(getModalHeaderWrapper()).toHaveLength(1);
});

test('contains a close button', () => {
expect(getModalHeaderWrapper().props().closeButton).toBe(true);
expect(getModalHeaderWrapper().props().closeLabel).toBe('ModalHeader.closeButtonText');
});

test('title is correct', () => {
const modalTitle = getModalHeaderWrapper().find(Modal.Title);
expect(modalTitle.length).toBe(1);
expect(modalTitle.prop('children')).toBe('ConfirmCashPaymentModal.title');
});
});

describe('Modal body', () => {
function getModalBodyWrapper(props) {
return getWrapper(props).find(Modal.Body);
}

test('is rendered', () => {
const body = getModalBodyWrapper();
expect(body).toHaveLength(1);
expect(body.prop('children')).toBe('ConfirmCashPaymentModal.body');
});
});

describe('Modal footer', () => {
test('is rendered', () => {
const footer = getWrapper().find(Modal.Footer);
expect(footer).toHaveLength(1);
});

test('buttons are rendered', () => {
const footer = getWrapper().find(Modal.Footer);
const buttons = footer.find(Button);
expect(buttons).toHaveLength(2);
expect(buttons.at(0).prop('bsStyle')).toBe('primary');
expect(buttons.at(0).prop('className')).toBe(defaultProps.fontSize);
expect(buttons.at(0).prop('onClick')).toBeDefined();
expect(buttons.at(0).prop('children')).toBe('common.back');

expect(buttons.at(1).prop('bsStyle')).toBe('success');
expect(buttons.at(1).prop('className')).toBe(defaultProps.fontSize);
expect(buttons.at(1).prop('onClick')).toBeDefined();
expect(buttons.at(1).prop('children')).toBe('common.confirmCashPayment');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.btn-success {
color: #008540;
border-color: #008540;
}
Loading