diff --git a/client/package.json b/client/package.json
index 26300b5..4aa04dc 100644
--- a/client/package.json
+++ b/client/package.json
@@ -5,6 +5,7 @@
"dependencies": {
"axios": "^0.18.0",
"fuse.js": "^3.4.5",
+ "moment": "^2.24.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.0.3",
diff --git a/client/src/components/DateRangePicker/index.js b/client/src/components/DateRangePicker/index.js
new file mode 100644
index 0000000..74d23f7
--- /dev/null
+++ b/client/src/components/DateRangePicker/index.js
@@ -0,0 +1,128 @@
+import React, { Component } from 'react';
+import moment from 'moment';
+
+import { Form, Message, Grid } from 'semantic-ui-react';
+
+import { defaultDateFormat } from '../../constants/constants';
+
+class DateRangePicker extends Component {
+ constructor(props) {
+ super(props);
+
+ const initialStartDate = props.startDate ? moment(props.startDate, defaultDateFormat) : moment().startOf('month');
+ let initialEndDate = props.endDate ? moment(props.endDate, defaultDateFormat) : moment();
+
+ if (initialStartDate > initialEndDate){
+ initialEndDate = initialStartDate.add(1, 'day');
+ }
+
+ this.state = {
+ startDate: initialStartDate,
+ endDate: initialEndDate,
+ error: null,
+ startDateLabel: props.startDateLabel || 'Start date',
+ endDateLabel: props.endDateLabel || 'End date',
+ };
+ }
+
+ onStartDateChange(event) {
+ const { endDate, startDateLabel, endDateLabel } = this.state;
+
+ const newStartDate = moment(event.target.value, defaultDateFormat);
+ if (newStartDate > endDate){
+ this.setState({
+ error: `${startDateLabel} cannot be after ${endDateLabel}`
+ });
+ return;
+ }
+ this.setState({startDate: newStartDate, error: null});
+ }
+
+ onEndDateChange(event) {
+ const { startDate, startDateLabel, endDateLabel } = this.state;
+
+ const newEndDate = moment(event.target.value, defaultDateFormat);
+ if (newEndDate < startDate){
+ this.setState({
+ error: `${startDateLabel} cannot be after ${endDateLabel}`
+ });
+ return;
+ }
+ this.setState({endDate: newEndDate, error: null});
+ }
+
+ onDismiss() {
+ this.setState({error: null});
+ }
+
+ onButtonClick() {
+ const { onDatesUpdate } = this.props;
+ const { startDate, endDate } = this.state;
+
+ if (onDatesUpdate){
+ onDatesUpdate({
+ startDate: startDate.format(defaultDateFormat),
+ endDate: endDate.format(defaultDateFormat),
+ });
+ }
+ }
+
+ componentDidMount() {
+ this.onButtonClick();
+ }
+
+ render() {
+ const { startDate, endDate, error, startDateLabel, endDateLabel } = this.state;
+
+ const buttonLabel = this.props.buttonLabel || 'Save';
+
+ const startDateValue = startDate.format(defaultDateFormat);
+ const endDateValue = endDate.format(defaultDateFormat);
+
+ return (
+
+ );
+ }
+}
+
+export default DateRangePicker;
diff --git a/client/src/components/MemberIncidentsTable/index.js b/client/src/components/MemberIncidentsTable/index.js
new file mode 100644
index 0000000..2e6b44f
--- /dev/null
+++ b/client/src/components/MemberIncidentsTable/index.js
@@ -0,0 +1,94 @@
+import React from 'react';
+import { Loader } from 'semantic-ui-react';
+import ReactTable from 'react-table';
+import 'react-table/react-table.css';
+
+import {incidentsReportHeaderTitles} from '../../constants/menuItems';
+import {
+ incidentDescriptions,
+ incidentLevelDescriptions,
+ UNLOCKED_INCIDENT,
+ UNSCHEDULED_INCIDENT
+} from '../../constants/enums';
+
+
+const MemberIncidentsTable = props => {
+ const { loading, title } = props;
+ const incidents = props.incidents ? props.incidents : [];
+
+ const columns = [];
+ if (incidents && incidents.length > 0){
+ const incidentHeaders = Object.keys(incidentsReportHeaderTitles);
+
+ incidentHeaders.forEach((header) => {
+ const columnTitle = incidentsReportHeaderTitles[header];
+
+ if (columnTitle){
+ const columnAlignments = {
+ left: 'left',
+ right: 'right',
+ };
+ let columnContentsAlignment = columnAlignments.left;
+
+ columns.push({
+ Header: incidentsReportHeaderTitles[header],
+ accessor: header,
+ Cell: props => {
+ let cellValue;
+
+ switch (props.column.id) {
+ case 'incidentType':
+ cellValue = incidentDescriptions[props.value];
+ break;
+ case 'incidentLevel':
+ cellValue = incidentLevelDescriptions[props.value];
+ break;
+ case 'feeDescription':
+ const { incidentType, incidentLevel, timeIntervalsToCharge } = props.row['_original'];
+
+ switch (incidentType) {
+ case UNLOCKED_INCIDENT:
+ cellValue = `${incidentLevelDescriptions[incidentLevel]}`;
+ break;
+ case UNSCHEDULED_INCIDENT:
+ cellValue = `${timeIntervalsToCharge} x 5 min`;
+ break;
+ default:
+ cellValue = '';
+ break;
+ }
+ break;
+ case 'totalChargeFee':
+ const totalFee = props.value ? props.value : props.row['_original'].incidentPrice;
+ const totalFeeFormatted = parseFloat(totalFee).toFixed(2);
+ cellValue = `$ ${totalFeeFormatted}`;
+ columnContentsAlignment = columnAlignments.right;
+ break;
+ default:
+ cellValue = props.value;
+ }
+
+ return {cellValue}
+ }
+ });
+ }
+ });
+ }
+
+ return (
+
+
{title}
+
+ {
+ !loading && incidents &&
+
+ }
+
+ );
+};
+
+export default MemberIncidentsTable;
diff --git a/client/src/constants/constants.js b/client/src/constants/constants.js
new file mode 100644
index 0000000..ed5633b
--- /dev/null
+++ b/client/src/constants/constants.js
@@ -0,0 +1 @@
+export const defaultDateFormat = 'YYYY-MM-DD';
diff --git a/client/src/constants/menuItems.js b/client/src/constants/menuItems.js
index 8a910e3..e2ab184 100644
--- a/client/src/constants/menuItems.js
+++ b/client/src/constants/menuItems.js
@@ -1,6 +1,7 @@
import UploadDLockData from '../scenes/UploadDLockData';
import Home from '../scenes/Home';
import IncidentsReport from '../scenes/IncidentsReport';
+import PracticeSummaryReport from '../scenes/PracticeSummaryReport';
export const mainMenuItems = [
{
@@ -9,6 +10,12 @@ export const mainMenuItems = [
url: '/',
component: Home,
},
+ {
+ id: 'practiceSummaryReport',
+ title: 'Practice Summary Report',
+ url: '/practice-summary-report',
+ component: PracticeSummaryReport,
+ },
{
id: 'report',
title: 'Incidents Report',
diff --git a/client/src/scenes/IncidentsReport/index.js b/client/src/scenes/IncidentsReport/index.js
index 09a76cb..6501d47 100644
--- a/client/src/scenes/IncidentsReport/index.js
+++ b/client/src/scenes/IncidentsReport/index.js
@@ -1,96 +1,30 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
-import { Container, Loader } from 'semantic-ui-react';
-import ReactTable from 'react-table';
-import 'react-table/react-table.css';
+import { Container } from 'semantic-ui-react';
import MainMenu from '../../components/MainMenu';
+import DateRangePicker from '../../components/DateRangePicker';
+import MemberIncidentsTable from '../../components/MemberIncidentsTable';
+
import { fetchIncidents } from '../../store/actions';
-import { incidentsReportHeaderTitles } from '../../constants/menuItems';
-import { incidentDescriptions, incidentLevelDescriptions, UNSCHEDULED_INCIDENT, UNLOCKED_INCIDENT } from '../../constants/enums';
class IncidentsReport extends Component {
-
- componentDidMount() {
+ onDatesUpdate(dateRange) {
const { fetchIncidents } = this.props;
- fetchIncidents();
+ fetchIncidents(dateRange);
}
render () {
const { pendingIncidents, incidents } = this.props;
- const columns = [];
- if (incidents && incidents.length > 0){
- const incidentHeaders = Object.keys(incidentsReportHeaderTitles);
-
- incidentHeaders.forEach((header) => {
- const columnTitle = incidentsReportHeaderTitles[header];
-
- if (columnTitle){
- const columnAlignments = {
- left: 'left',
- right: 'right',
- };
- let columnContentsAlignment = columnAlignments.left;
-
- columns.push({
- Header: incidentsReportHeaderTitles[header],
- accessor: header,
- Cell: props => {
- let cellValue;
-
- switch (props.column.id) {
- case 'incidentType':
- cellValue = incidentDescriptions[props.value];
- break;
- case 'incidentLevel':
- cellValue = incidentLevelDescriptions[props.value];
- break;
-
- case 'feeDescription':
- const { incidentType, incidentLevel, timeIntervalsToCharge } = props.row['_original'];
- switch (incidentType) {
- case UNLOCKED_INCIDENT:
- cellValue = `${incidentLevelDescriptions[incidentLevel]}`;
- break;
- case UNSCHEDULED_INCIDENT:
- cellValue = `${timeIntervalsToCharge} x 5 min`;
- break;
- }
- break;
-
- case 'totalChargeFee':
- const totalFee = props.value ? props.value : props.row['_original'].incidentPrice;
- const totalFeeFormatted = parseFloat(totalFee).toFixed(2);
- cellValue = `$ ${totalFeeFormatted}`;
- columnContentsAlignment = columnAlignments.right;
- break;
-
- default:
- cellValue = props.value;
- }
-
- return {cellValue}
- }
- });
- }
- });
- }
-
return (
Incidents Report
-
- {
- !pendingIncidents && incidents &&
-
- }
+
+
+
);
}
@@ -102,7 +36,7 @@ const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
- fetchIncidents: () => fetchIncidents(dispatch),
+ fetchIncidents: (dateRange) => fetchIncidents(dispatch, dateRange),
});
export default connect(mapStateToProps, mapDispatchToProps)(IncidentsReport);
diff --git a/client/src/scenes/PracticeSummaryReport/components/MemberSelector.js b/client/src/scenes/PracticeSummaryReport/components/MemberSelector.js
new file mode 100644
index 0000000..2c439b3
--- /dev/null
+++ b/client/src/scenes/PracticeSummaryReport/components/MemberSelector.js
@@ -0,0 +1,58 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { Dropdown, Form } from 'semantic-ui-react';
+
+import { fetchMembersList } from '../../../store/actions';
+
+class MemberSelector extends Component {
+ componentDidMount() {
+ const { fetchMembersList } = this.props;
+
+ fetchMembersList();
+ }
+
+ onMemberSelectionChange(event, data){
+ const { onMemberSelect } = this.props;
+
+ const { value } = data;
+
+ if (onMemberSelect && value){
+ onMemberSelect(value);
+ }
+ }
+
+ render(){
+ const { members } = this.props;
+
+ const dropdownOptions = members && Array.isArray(members) ? members.map(member => ({
+ key: member.memberId,
+ value: member.memberId,
+ text: member.name
+ })
+ ) : null;
+
+ return (
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => ({
+ members: state.membersList.result,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ fetchMembersList: () => fetchMembersList(dispatch),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(MemberSelector);
diff --git a/client/src/scenes/PracticeSummaryReport/components/MemberSummary.js b/client/src/scenes/PracticeSummaryReport/components/MemberSummary.js
new file mode 100644
index 0000000..3be23d6
--- /dev/null
+++ b/client/src/scenes/PracticeSummaryReport/components/MemberSummary.js
@@ -0,0 +1,9 @@
+import React, { Component } from 'react';
+
+class MemberSummary extends Component {
+ render() {
+ return (Member Summary
);
+ }
+}
+
+export default MemberSummary;
diff --git a/client/src/scenes/PracticeSummaryReport/index.js b/client/src/scenes/PracticeSummaryReport/index.js
new file mode 100644
index 0000000..baa1e42
--- /dev/null
+++ b/client/src/scenes/PracticeSummaryReport/index.js
@@ -0,0 +1,87 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import {Container, Grid} from 'semantic-ui-react';
+
+import MainMenu from '../../components/MainMenu';
+import DateRangePicker from '../../components/DateRangePicker';
+import MemberSelector from './components/MemberSelector';
+import MemberSummary from './components/MemberSummary';
+import MemberIncidentsTable from '../../components/MemberIncidentsTable';
+
+import { fetchMemberIncidents } from '../../store/actions';
+
+class PracticeSummaryReport extends Component {
+ constructor(props){
+ super(props);
+
+ this.state = {
+ dateRange: null,
+ memberId: null,
+ };
+ }
+
+ onDateRangeUpdate(dateRange){
+ this.fetchIncidents(dateRange, this.state.memberId);
+ this.setState({dateRange});
+ }
+
+ onMemberSelectionUpdate(memberId){
+ this.fetchIncidents(this.state.dateRange, memberId);
+ this.setState({memberId});
+ }
+
+ fetchIncidents(dateRange, memberId){
+ const { fetchMemberIncidents } = this.props;
+
+ if (dateRange && dateRange.startDate && dateRange.endDate && memberId){
+ fetchMemberIncidents(memberId, dateRange);
+ }
+ }
+
+ render () {
+ const { memberIncidents, loading } = this.props;
+
+ return (
+
+
+ Practice Summary Report
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => ({
+ memberIncidents: state.memberIncidents.result,
+ loading: state.memberIncidents.pending,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ fetchMemberIncidents: (memberId, dateRange) => fetchMemberIncidents(dispatch, memberId, dateRange),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(PracticeSummaryReport);
diff --git a/client/src/store/actions/integrationActions.js b/client/src/store/actions/integrationActions.js
index 599573f..5e9a127 100644
--- a/client/src/store/actions/integrationActions.js
+++ b/client/src/store/actions/integrationActions.js
@@ -8,6 +8,12 @@ import {
FETCH_INCIDENTS_PENDING,
FETCH_INCIDENTS_SUCCESS,
FETCH_INCIDENTS_FAILED,
+ FETCH_MEMBERS_PENDING,
+ FETCH_MEMBERS_SUCCESS,
+ FETCH_MEMBERS_FAILED,
+ FETCH_MEMBER_INCIDENTS_PENDING,
+ FETCH_MEMBER_INCIDENTS_SUCCESS,
+ FETCH_MEMBER_INCIDENTS_FAILED,
} from '../constants';
import API from '../../utilities/api';
@@ -36,9 +42,11 @@ export const addNewMapping = (dispatch, mapping) => {
});
};
-export const fetchIncidents = (dispatch) => {
+export const fetchIncidents = (dispatch, dateRange) => {
+ const { startDate, endDate } = dateRange;
+
dispatch({type: FETCH_INCIDENTS_PENDING});
- API.get('integration/report/allIncidents')
+ API.get(`integration/report/allIncidents/${startDate}/${endDate}`)
.then(response => {
dispatch({type: FETCH_INCIDENTS_SUCCESS, payload: response.data});
})
@@ -46,3 +54,27 @@ export const fetchIncidents = (dispatch) => {
dispatch({type: FETCH_INCIDENTS_FAILED, payload: error.response});
});
};
+
+export const fetchMembersList = (dispatch) => {
+ dispatch({type: FETCH_MEMBERS_PENDING});
+ API.get('officeRnD/membersList')
+ .then(response => {
+ dispatch({type: FETCH_MEMBERS_SUCCESS, payload: response.data});
+ })
+ .catch(error => {
+ dispatch({type: FETCH_MEMBERS_FAILED, payload: error.response});
+ });
+};
+
+export const fetchMemberIncidents = (dispatch, memberId, dateRange) => {
+ const { startDate, endDate } = dateRange;
+
+ dispatch({type: FETCH_MEMBER_INCIDENTS_PENDING});
+ API.get(`integration/report/member/${memberId}/${startDate}/${endDate}`)
+ .then(response => {
+ dispatch({type: FETCH_MEMBER_INCIDENTS_SUCCESS, payload: response.data});
+ })
+ .catch(error => {
+ dispatch({type: FETCH_MEMBER_INCIDENTS_FAILED, payload: error.response});
+ });
+};
diff --git a/client/src/store/constants.js b/client/src/store/constants.js
index 9b20bbc..6dd4fd8 100644
--- a/client/src/store/constants.js
+++ b/client/src/store/constants.js
@@ -13,3 +13,11 @@ export const ADD_NEW_MAPPING_FAILED = 'ADD_NEW_MAPPING_FAILED';
export const FETCH_INCIDENTS_PENDING = 'FETCH_INCIDENTS_PENDING';
export const FETCH_INCIDENTS_SUCCESS = 'FETCH_INCIDENTS_SUCCESS';
export const FETCH_INCIDENTS_FAILED = 'FETCH_INCIDENTS_FAILED';
+
+export const FETCH_MEMBERS_PENDING = 'FETCH_MEMBERS_PENDING';
+export const FETCH_MEMBERS_SUCCESS = 'FETCH_MEMBERS_SUCCESS';
+export const FETCH_MEMBERS_FAILED = 'FETCH_MEMBERS_FAILED';
+
+export const FETCH_MEMBER_INCIDENTS_PENDING = 'FETCH_MEMBER_INCIDENTS_PENDING';
+export const FETCH_MEMBER_INCIDENTS_SUCCESS = 'FETCH_MEMBER_INCIDENTS_SUCCESS';
+export const FETCH_MEMBER_INCIDENTS_FAILED = 'FETCH_MEMBER_INCIDENTS_FAILED';
diff --git a/client/src/store/reducers/index.js b/client/src/store/reducers/index.js
index 3c060be..869300c 100644
--- a/client/src/store/reducers/index.js
+++ b/client/src/store/reducers/index.js
@@ -4,11 +4,15 @@ import { doorLockData} from './doorLockReducers';
import { mappingsData } from './mappingsReducer';
import { addMapping } from './addMappingReducer';
import { incidentsReport } from './incidentsReportReducer';
+import { membersList } from './membersListReducer';
+import { memberIncidents} from './memberIncidentsReducer';
export const rootReducer = combineReducers({
doorLockData,
mappingsData,
addMapping,
incidentsReport,
+ membersList,
+ memberIncidents,
});
diff --git a/client/src/store/reducers/memberIncidentsReducer.js b/client/src/store/reducers/memberIncidentsReducer.js
new file mode 100644
index 0000000..04cef0f
--- /dev/null
+++ b/client/src/store/reducers/memberIncidentsReducer.js
@@ -0,0 +1,38 @@
+import {
+ FETCH_MEMBER_INCIDENTS_PENDING,
+ FETCH_MEMBER_INCIDENTS_SUCCESS,
+ FETCH_MEMBER_INCIDENTS_FAILED,
+} from '../constants';
+
+const initialState = {
+ pending: false,
+ result: null,
+ error: null,
+};
+
+export const memberIncidents = (state, action) => {
+ state = state || initialState;
+ action = action || {};
+
+ switch(action.type){
+ case FETCH_MEMBER_INCIDENTS_PENDING:
+ return Object.assign({}, state, {
+ pending: true,
+ error: null,
+ });
+ case FETCH_MEMBER_INCIDENTS_SUCCESS:
+ return Object.assign({}, state, {
+ pending: false,
+ result: action.payload,
+ error: null,
+ });
+ case FETCH_MEMBER_INCIDENTS_FAILED:
+ return Object.assign({}, state, {
+ pending: false,
+ result: {},
+ error: action.payload,
+ });
+ default:
+ return state;
+ }
+};
diff --git a/client/src/store/reducers/membersListReducer.js b/client/src/store/reducers/membersListReducer.js
new file mode 100644
index 0000000..bf5c1f6
--- /dev/null
+++ b/client/src/store/reducers/membersListReducer.js
@@ -0,0 +1,38 @@
+import {
+ FETCH_MEMBERS_PENDING,
+ FETCH_MEMBERS_SUCCESS,
+ FETCH_MEMBERS_FAILED,
+} from '../constants';
+
+const initialState = {
+ pending: false,
+ result: null,
+ error: null,
+};
+
+export const membersList = (state, action) => {
+ state = state || initialState;
+ action = action || {};
+
+ switch(action.type){
+ case FETCH_MEMBERS_PENDING:
+ return Object.assign({}, state, {
+ pending: true,
+ error: null,
+ });
+ case FETCH_MEMBERS_SUCCESS:
+ return Object.assign({}, state, {
+ pending: false,
+ result: action.payload,
+ error: null,
+ });
+ case FETCH_MEMBERS_FAILED:
+ return Object.assign({}, state, {
+ pending: false,
+ result: {},
+ error: action.payload,
+ });
+ default:
+ return state;
+ }
+};
diff --git a/client/yarn.lock b/client/yarn.lock
index dd8a44d..caa1ce2 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -5478,6 +5478,10 @@ mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@
dependencies:
minimist "0.0.8"
+moment@^2.24.0:
+ version "2.24.0"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
+
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
diff --git a/constants/constants.js b/constants/constants.js
index 1650930..812c38a 100644
--- a/constants/constants.js
+++ b/constants/constants.js
@@ -56,6 +56,7 @@ const integrationServiceErrors = {
FAILED_TO_SAVE_BOOKINGS: 'Failed to save booking reservations',
FAILED_TO_SAVE_DOOR_LOCK_ENTRIES: 'Failed to save door lock entries',
FAILED_TO_SAVE_DATA_GENERIC: 'Failed to save data',
+ INVALID_DATE_RANGE: 'Dates in date range are invalid',
};
const incidentType = {
@@ -64,6 +65,10 @@ const incidentType = {
UNSCHEDULED_INCIDENT: 3,
};
+const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles';
+
+const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
+
module.exports = {
VALID_CSV_HEADERS,
USER_ENTRY_EVENT,
@@ -75,4 +80,6 @@ module.exports = {
unlockedIncidentLevelsPrices,
integrationServiceErrors,
incidentType,
+ UI_TIMEZONE,
+ DEFAULT_DATE_FORMAT,
};
diff --git a/controllers/integration.js b/controllers/integration.js
index ddaf1cf..fd2cde0 100644
--- a/controllers/integration.js
+++ b/controllers/integration.js
@@ -33,7 +33,29 @@ const addNewMapping = (req, res) => {
};
const getAllIncidents = (req, res) => {
- getAllDoorLockIncidents()
+ const dateRange = {
+ startDate: req.params.startDate,
+ endDate: req.params.endDate,
+ };
+
+ getAllDoorLockIncidents(dateRange)
+ .then((incidents) => {
+ res.send(incidents);
+ })
+ .catch((error) => {
+ console.log(error);
+ res.send([]);
+ });
+};
+
+const getMemberIncidents = (req, res) => {
+ const memberId = req.params.memberId;
+ const dateRange = {
+ startDate: req.params.startDate,
+ endDate: req.params.endDate,
+ };
+
+ getAllDoorLockIncidents(dateRange, memberId)
.then((incidents) => {
res.send(incidents);
})
@@ -57,4 +79,5 @@ module.exports = {
getAllIncidents,
getUnscheduledIncidents,
getUnlockedIncidents,
+ getMemberIncidents,
};
diff --git a/controllers/officeRnD.js b/controllers/officeRnD.js
new file mode 100644
index 0000000..a2a9a22
--- /dev/null
+++ b/controllers/officeRnD.js
@@ -0,0 +1,18 @@
+'use strict';
+
+const { fetchAllMembers } = require('../services/officeRnD/members');
+
+const fetchMembersList = (req, res) => {
+ fetchAllMembers()
+ .then((members) => {
+ res.send(members);
+ })
+ .catch((error) => {
+ console.log(error);
+ res.send([]);
+ });
+};
+
+module.exports = {
+ fetchMembersList,
+};
diff --git a/routes/index.js b/routes/index.js
index c1b82eb..abe717c 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -2,7 +2,9 @@
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
const { uploadDoorLockData } = require('../controllers/doorLock');
-const { getKnownOfficeResourceMappings, addNewMapping, getAllIncidents,getUnscheduledIncidents, getUnlockedIncidents } = require('../controllers/integration');
+const { getKnownOfficeResourceMappings, addNewMapping, getAllIncidents, getMemberIncidents,getUnscheduledIncidents, getUnlockedIncidents } = require('../controllers/integration');
+const { fetchMembersList } = require('../controllers/officeRnD');
+
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
const express = require('express');
@@ -14,10 +16,13 @@ router.post('/doorLock/upload', uploadDoorLockData);
router.get('/integration/mappings', getKnownOfficeResourceMappings);
router.post('/integration/mappings', addNewMapping);
-router.get('/integration/report/allIncidents', getAllIncidents);
+router.get('/integration/report/member/:memberId/:startDate/:endDate', getMemberIncidents);
+router.get('/integration/report/allIncidents/:startDate/:endDate', getAllIncidents);
router.get('/integration/report/unlockedIncidents', getUnlockedIncidents);
router.get('/integration/report/unscheduledIncidents', getUnscheduledIncidents);
+router.get('/officeRnD/membersList', fetchMembersList);
+
// temporary route, manually trigger door lock charge calculations
router.get('/calculate', (req, res) => { calculateDoorLockCharges(); res.send();});
diff --git a/services/integration/reports.js b/services/integration/reports.js
index ce54691..eee8f68 100644
--- a/services/integration/reports.js
+++ b/services/integration/reports.js
@@ -3,23 +3,41 @@
const moment = require('moment-timezone');
const db = require('../../models/index');
-const { incidentType } = require('../../constants/constants');
+const Op = require('sequelize').Op;
+
+const { incidentType, UI_TIMEZONE, DEFAULT_DATE_FORMAT, integrationServiceErrors } = require('../../constants/constants');
const { fetchAllMembers } = require('../officeRnD/members');
const { fetchOffices, fetchResources } = require('../officeRnD/resources');
-const getUnlockedIncidents = () => {
+const getUnlockedIncidents = (startDate, endDate, memberId) => {
const attributes = ['id', 'memberId', 'resourceId', 'bookingStart', 'bookingEnd', 'incidentLevel', 'incidentLevelPrice'];
+ const filters = {};
+
+ if (startDate && endDate) {
+ filters.bookingStart = {
+ [Op.and]: {
+ [Op.gte]: startDate.utc().toISOString(),
+ [Op.lte]: endDate.utc().toISOString(),
+ }
+ }
+ }
+
+ if (memberId){
+ filters.memberId = memberId;
+ }
+
return db.unlockedIncident.findAll({
attributes,
+ where: filters,
sort: [
['bookingStart', 'ASC']
]
});
};
-const getUnscheduledIncidents = () => {
+const getUnscheduledIncidents = (startDate, endDate, memberId) => {
const attributes = [
'id',
'memberId',
@@ -33,8 +51,24 @@ const getUnscheduledIncidents = () => {
'totalChargeFee'
];
+ const filters = {};
+
+ if (startDate && endDate) {
+ filters.bookingStart = {
+ [Op.and]: {
+ [Op.gte]: startDate.utc().toISOString(),
+ [Op.lte]: endDate.utc().toISOString(),
+ }
+ }
+ }
+
+ if (memberId){
+ filters.memberId = memberId;
+ }
+
return db.unscheduledIncident.findAll({
attributes,
+ where: filters,
sort: [
['bookingStart', 'ASC']
]
@@ -42,13 +76,24 @@ const getUnscheduledIncidents = () => {
};
const formatTime = (timestamp) => {
- const timezone = process.env.UI_TIMEZONE || 'America/Los_Angeles';
- return moment.tz(timestamp, timezone).format('MM/DD/YYYY hh:mm a');
+ return moment.tz(timestamp, UI_TIMEZONE).format('MM/DD/YYYY hh:mm a');
};
-const getAllDoorLockIncidents = () => {
+const getAllDoorLockIncidents = (dateRange, memberId) => {
return new Promise ((resolve, reject) => {
- const dataFetchJobs = [fetchAllMembers(), fetchOffices(), fetchResources(), getUnlockedIncidents(), getUnscheduledIncidents()];
+ let startDate, endDate;
+
+ if (dateRange.startDate && dateRange.endDate){
+ startDate = moment.tz(dateRange.startDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE);
+ endDate = moment.tz(dateRange.endDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE);
+
+ if (!startDate.isValid() || !endDate.isValid() || endDate.isBefore(startDate)){
+ reject(integrationServiceErrors.INVALID_DATE_RANGE);
+ return;
+ }
+ }
+
+ const dataFetchJobs = [fetchAllMembers(), fetchOffices(), fetchResources(), getUnlockedIncidents(startDate, endDate, memberId), getUnscheduledIncidents(startDate, endDate, memberId)];
Promise.all(dataFetchJobs)
.then((data) => {