Skip to content

Commit

Permalink
Reservation rate excel report (#338)
Browse files Browse the repository at this point in the history
* Create Reservation rate report api action

Create the action for fetching the reservation
rate report excel.

Also add extensions for filenames for all report actions
and modify the downloadReport function to not append
.docx to filename anymore. This had to be done because
all previous reports are .docx but this new one is xlsx.

* Conditional datepicker rendering in DateTimeRange

Conditionally render datepicker in DateTimeRange
component. Defaults to always rendering it unless
specifying not to render it in controlprops.

The Time range part of the DateTimeRange component
is nice and useful, so now basically you are able
to just render that part.

* Reservation Rate Report actions, reducers, selectors

Create actions reducers and selectors for Modal.

* Create reservation rate report components

Create component ReservationRateReportModal. This modal lies
in the /reservations page and can be unhidden/shown in
the dropdown of <Lataa Raportti> -> Varausasteraportti.

This modal will take the basic props the modal itself requires
along with units and the action which downloads the report.

Within the modal there are fields where the user can select/
input data: Units, Date range and Time range. Unit seletion field
is a Typeahead component which is also a new package. Lastly
there is the download button which when clicked, will call
the api action to download the report.

* Fix errors

Errors were mostly due to incorrect proptyping

* Project conf

Install node-sass that is compatible with
node version 14.X.X

Add a css loader for testing.

Co-authored-by: Kevin Seestrand <kevin.seestrand@anders.fi>
  • Loading branch information
HotStew and Kevin Seestrand committed Apr 28, 2022
1 parent fc60035 commit 8e0e08a
Show file tree
Hide file tree
Showing 24 changed files with 628 additions and 33 deletions.
3 changes: 3 additions & 0 deletions app/actions/uiActions.js
Expand Up @@ -3,17 +3,20 @@ import { createActions } from 'redux-actions';
export default createActions(
'CHANGE_RESERVATION_SEARCH_FILTERS',
'CHANGE_RESOURCE_SEARCH_FILTERS',
'CHANGE_RESERVATIONS_RATE_REPORT_MODAL_FILTERS',
'CLEAR_RESOURCE_SELECTOR',
'HIDE_RESERVATION_CANCEL_MODAL',
'HIDE_RESERVATION_INFO_MODAL',
'HIDE_RESERVATION_SUCCESS_MODAL',
'HIDE_RESOURCE_INFO_MODAL',
'HIDE_RESOURCE_IMAGES_MODAL',
'HIDE_RESOURCE_SELECTOR_MODAL',
'HIDE_RESERVATIONS_RATE_REPORT_MODAL',
'SHOW_RESERVATION_CANCEL_MODAL',
'SHOW_RESERVATION_INFO_MODAL',
'SHOW_RESERVATION_SUCCESS_MODAL',
'SHOW_RESOURCE_INFO_MODAL',
'SHOW_RESOURCE_IMAGES_MODAL',
'SHOW_RESOURCE_SELECTOR_MODAL',
'SHOW_RESERVATIONS_RATE_REPORT_MODAL',
);
1 change: 1 addition & 0 deletions app/api/actionTypes.js
Expand Up @@ -21,6 +21,7 @@ export default Object.assign(
create('RESERVATION', ['GET', 'DELETE', 'POST', 'PUT']),
create('RESERVATION_DETAILS_REPORT', ['GET']),
create('RESERVATIONS_REPORT', ['GET']),
create('RESERVATIONS_RATE_REPORT', ['GET']),
create('RESERVATIONS', ['GET']),
create('RESOURCE', ['GET']),
create('RESOURCE_DAILY_REPORT', ['GET']),
Expand Down
2 changes: 2 additions & 0 deletions app/api/actions/index.js
Expand Up @@ -13,6 +13,7 @@ import {
fetchReservationDetailsReport,
fetchReservationsReport,
fetchResourceDailyReport,
fetchReservationsRateReport,
} from './reports';
import {
cancelReservation,
Expand Down Expand Up @@ -48,6 +49,7 @@ export {
fetchReservationDetailsReport,
fetchReservationsReport,
fetchResourceDailyReport,
fetchReservationsRateReport,
fetchReservation,
fetchReservations,
fetchResource,
Expand Down
16 changes: 13 additions & 3 deletions app/api/actions/reports.js
Expand Up @@ -6,7 +6,7 @@ function fetchResourceDailyReport({ date, resourceIds }) {
const day = moment(date).format('YYYY-MM-DD');
return createReportAction({
endpoint: 'daily_reservations',
filename: `paivaraportti-${day}`,
filename: `paivaraportti-${day}.docx`,
type: 'RESOURCE_DAILY_REPORT',
params: {
resource: resourceIds.join(','),
Expand All @@ -19,7 +19,7 @@ function fetchResourceDailyReport({ date, resourceIds }) {
function fetchReservationDetailsReport(reservationId) {
return createReportAction({
endpoint: 'reservation_details',
filename: `varaus-${reservationId}`,
filename: `varaus-${reservationId}.docx`,
type: 'RESERVATION_DETAILS_REPORT',
params: {
reservation: reservationId,
Expand All @@ -30,14 +30,24 @@ function fetchReservationDetailsReport(reservationId) {
function fetchReservationsReport(filters) {
return createReportAction({
endpoint: 'reservation_details',
filename: 'varaukset',
filename: 'varaukset.docx',
type: 'RESERVATIONS_REPORT',
params: filters,
});
}

function fetchReservationsRateReport(filters) {
return createReportAction({
endpoint: 'reservation_rate',
filename: 'varausasteraportti.xlsx',
type: 'RESERVATIONS_RATE_REPORT',
params: filters,
});
}

export {
fetchReservationDetailsReport,
fetchResourceDailyReport,
fetchReservationsReport,
fetchReservationsRateReport,
};
2 changes: 1 addition & 1 deletion app/api/actions/utils.js
Expand Up @@ -98,7 +98,7 @@ function getSuccessTypeDescriptor(type, options = {}) {

function downloadReport(response, filename) {
response.blob().then((blob) => {
fileSaver.saveAs(blob, `${filename}.docx`);
fileSaver.saveAs(blob, filename);
});
}

Expand Down
2 changes: 2 additions & 0 deletions app/pages/AppContainer.js
Expand Up @@ -15,6 +15,7 @@ import {
import { fetchAuthState } from 'auth/actions';
import ReservationCancelModal from 'shared/modals/reservation-cancel';
import ReservationInfoModal from 'shared/modals/reservation-info';
import ReservationsRateReportModal from 'shared/modals/reservation-rate-report';
import ReservationSuccessModal from 'shared/modals/reservation-success';
import ResourceImagesModal from 'shared/modals/resource-images';
import ResourceInfoModal from 'shared/modals/resource-info';
Expand Down Expand Up @@ -55,6 +56,7 @@ export class UnconnectedAppContainer extends Component {
</Grid>
</Loader>
<ReservationInfoModal />
<ReservationsRateReportModal />
<ReservationCancelModal />
<ResourceImagesModal />
<ResourceInfoModal />
Expand Down
1 change: 1 addition & 0 deletions app/shared/_shared.scss
Expand Up @@ -11,6 +11,7 @@
@import './modals/reservation-info/reservation-info';
@import './modals/reservation-success/reservation-success';
@import './modals/resource-selector/resource-selector';
@import './modals/reservation-rate-report/reservation-rate-report';
@import './navbar/navbar';
@import './recurring-reservation-controls/recurring-reservation-controls';
@import './resource-info-button/resource-info-button';
Expand Down
Expand Up @@ -12,7 +12,7 @@ AvailabilityViewResourceInfo.propTypes = {
isFavorite: PropTypes.bool.isRequired,
isHighlighted: PropTypes.bool,
name: PropTypes.string.isRequired,
peopleCapacity: PropTypes.number.isRequired,
peopleCapacity: PropTypes.number,
};
export function AvailabilityViewResourceInfo(props) {
return (
Expand Down
Expand Up @@ -11,8 +11,8 @@ import Link from './Link';
Reservation.propTypes = {
begin: PropTypes.string.isRequired,
end: PropTypes.string.isRequired,
visualBegin: PropTypes.object,
visualEnd: PropTypes.object,
visualBegin: PropTypes.string,
visualEnd: PropTypes.string,
eventSubject: PropTypes.string,
id: PropTypes.number.isRequired,
numberOfParticipants: PropTypes.number,
Expand Down
25 changes: 15 additions & 10 deletions app/shared/form-fields/DateTimeRange.js
Expand Up @@ -12,6 +12,7 @@ export default class DateTimeRange extends React.Component {
onBlur: PropTypes.func,
onChange: PropTypes.func.isRequired,
required: PropTypes.bool,
renderDatePicker: PropTypes.bool,
value: PropTypes.shape({
begin: PropTypes.object.isRequired,
end: PropTypes.object.isRequired,
Expand Down Expand Up @@ -55,6 +56,8 @@ export default class DateTimeRange extends React.Component {

render() {
const value = this.props.controlProps.value;
const renderDatePicker = this.props.controlProps.renderDatePicker === undefined
? true : this.props.controlProps.renderDatePicker;
const requiredPostfix = this.props.controlProps.required ? '*' : '';
return (
<div
Expand All @@ -64,16 +67,18 @@ export default class DateTimeRange extends React.Component {
)}
>
<div className="date-time-range-field">
<Field
componentClass={DatePicker}
controlProps={{
onBlur: this.handleBlur,
onChange: this.handleDateChange,
value: value.begin.date,
}}
id={`${this.props.id}-date`}
label={this.props.noLabels ? '' : `Päivä${requiredPostfix}`}
/>
{renderDatePicker === true ? (
<Field
componentClass={DatePicker}
controlProps={{
onBlur: this.handleBlur,
onChange: this.handleDateChange,
value: value.begin.date,
}}
id={`${this.props.id}-date`}
label={this.props.noLabels ? '' : `Päivä${requiredPostfix}`}
/>
) : null}
<Field
componentClass={Time}
controlProps={{
Expand Down
29 changes: 21 additions & 8 deletions app/shared/form-fields/DateTimeRange.spec.js
Expand Up @@ -8,15 +8,16 @@ import DateTimeRange from './DateTimeRange';
import Field from './Field';
import Time from './Time';

const defaults = {
controlProps: {
onChange: () => null,
value: { begin: {}, end: {} },
},
id: '1234',
noLabels: false,
};

function getWrapper(props) {
const defaults = {
controlProps: {
onChange: () => null,
value: { begin: {}, end: {} },
},
id: '1234',
noLabels: false,
};
return shallow(<DateTimeRange {...defaults} {...props} />);
}

Expand All @@ -39,6 +40,18 @@ describe('shared/form-fields/DateTimeRange', () => {
expect(fields.at(2).prop('componentClass')).to.equal(Time);
});

it('does not render datepicker if specified', () => {
const fields = getWrapper({
controlProps: {
...defaults.controlProps,
renderDatePicker: false,
},
}).find(Field);
expect(fields).to.have.length(2);
expect(fields.at(0).prop('componentClass')).to.equal(Time);
expect(fields.at(1).prop('componentClass')).to.equal(Time);
});

it('renders labels only when noLabels = false', () => {
const fields = getWrapper().find(Field);
expect(fields.at(0).prop('label')).to.equal('Päivä');
Expand Down

0 comments on commit 8e0e08a

Please sign in to comment.