diff --git a/client/src/components/DateRangePicker/index.js b/client/src/components/DateRangePicker/index.js index 74d23f7..cb2d2c4 100644 --- a/client/src/components/DateRangePicker/index.js +++ b/client/src/components/DateRangePicker/index.js @@ -73,17 +73,25 @@ class DateRangePicker extends Component { render() { const { startDate, endDate, error, startDateLabel, endDateLabel } = this.state; + const { inlineButton } = this.props; const buttonLabel = this.props.buttonLabel || 'Save'; const startDateValue = startDate.format(defaultDateFormat); const endDateValue = endDate.format(defaultDateFormat); + const buttonRender = ( + + { inlineButton && } + {buttonLabel} + + ); + return (
- + - + - + + { + inlineButton && buttonRender + } { error && @@ -114,11 +125,12 @@ class DateRangePicker extends Component { } - - - {buttonLabel} - - + { + !inlineButton && + + {buttonRender} + + } ); diff --git a/client/src/components/MainMenu/index.js b/client/src/components/MainMenu/index.js index 6890a4c..1fd8f65 100644 --- a/client/src/components/MainMenu/index.js +++ b/client/src/components/MainMenu/index.js @@ -7,14 +7,19 @@ import { mainMenuItems } from '../../constants/menuItems'; const MainMenu = () => ( { - mainMenuItems.map(mainMenuItem => - - {mainMenuItem.title} - ) + mainMenuItems.map(mainMenuItem => { + if (!mainMenuItem.showInMenu){ + return null; + } else { + return ( + + {mainMenuItem.title} + ) + }}) } ); diff --git a/client/src/components/MemberIncidentsTable/index.js b/client/src/components/MemberIncidentsTable/index.js index 2e6b44f..6f79f59 100644 --- a/client/src/components/MemberIncidentsTable/index.js +++ b/client/src/components/MemberIncidentsTable/index.js @@ -2,6 +2,7 @@ import React from 'react'; import { Loader } from 'semantic-ui-react'; import ReactTable from 'react-table'; import 'react-table/react-table.css'; +import { NavLink } from 'react-router-dom'; import {incidentsReportHeaderTitles} from '../../constants/menuItems'; import { @@ -13,7 +14,7 @@ import { const MemberIncidentsTable = props => { - const { loading, title } = props; + const { loading, title, openMemberSummaryOnMemberClick } = props; const incidents = props.incidents ? props.incidents : []; const columns = []; @@ -34,9 +35,15 @@ const MemberIncidentsTable = props => { Header: incidentsReportHeaderTitles[header], accessor: header, Cell: props => { - let cellValue; + let cellValue = ''; + let urlValue = undefined; switch (props.column.id) { + case 'memberName': + const memberId = props.row['_original'].memberId; + urlValue = `/practice-summary-report/${memberId}`; + cellValue = props.value; + break; case 'incidentType': cellValue = incidentDescriptions[props.value]; break; @@ -68,7 +75,17 @@ const MemberIncidentsTable = props => { cellValue = props.value; } - return
{cellValue}
+ if (openMemberSummaryOnMemberClick && urlValue){ + return {cellValue} + }else{ + return
{cellValue}
+ } + + // return + //
{cellValue}
+ //
+ + // return
{cellValue}
} }); } diff --git a/client/src/constants/menuItems.js b/client/src/constants/menuItems.js index e2ab184..07b6c96 100644 --- a/client/src/constants/menuItems.js +++ b/client/src/constants/menuItems.js @@ -6,24 +6,34 @@ import PracticeSummaryReport from '../scenes/PracticeSummaryReport'; export const mainMenuItems = [ { id: 'home', + showInMenu: true, title: 'Home', url: '/', component: Home, }, { id: 'practiceSummaryReport', + showInMenu: true, title: 'Practice Summary Report', url: '/practice-summary-report', component: PracticeSummaryReport, }, + { + id: 'practiceSummaryReportWithMemberId', + showInMenu: false, + url: '/practice-summary-report/:memberId', + component: PracticeSummaryReport, + }, { id: 'report', + showInMenu: true, title: 'Incidents Report', url: '/incidents-report', component: IncidentsReport, }, { id: 'uploadDLockData', + showInMenu: true, title: 'DLock', url: '/dlock', component: UploadDLockData, diff --git a/client/src/scenes/IncidentsReport/index.js b/client/src/scenes/IncidentsReport/index.js index 6501d47..fa464ac 100644 --- a/client/src/scenes/IncidentsReport/index.js +++ b/client/src/scenes/IncidentsReport/index.js @@ -24,7 +24,7 @@ class IncidentsReport extends Component {

- + ); } diff --git a/client/src/scenes/PracticeSummaryReport/components/MemberSummary.js b/client/src/scenes/PracticeSummaryReport/components/MemberSummary.js index 687fed0..6f15622 100644 --- a/client/src/scenes/PracticeSummaryReport/components/MemberSummary.js +++ b/client/src/scenes/PracticeSummaryReport/components/MemberSummary.js @@ -11,7 +11,6 @@ const MemberSummary = props => { let totalUnlockedFees = 0; incidents.forEach((incident) => { - console.log(incident); switch (incident.incidentType) { case UNSCHEDULED_INCIDENT: totalUnscheduledFees += parseFloat(incident.totalChargeFee); @@ -19,6 +18,8 @@ const MemberSummary = props => { case UNLOCKED_INCIDENT: totalUnlockedFees += parseFloat(incident.incidentPrice); break; + default: + break; } }); @@ -65,5 +66,4 @@ const MemberSummary = props => { ); }; - export default MemberSummary; diff --git a/client/src/scenes/PracticeSummaryReport/index.js b/client/src/scenes/PracticeSummaryReport/index.js index 70fa155..32ea48e 100644 --- a/client/src/scenes/PracticeSummaryReport/index.js +++ b/client/src/scenes/PracticeSummaryReport/index.js @@ -16,7 +16,7 @@ class PracticeSummaryReport extends Component { this.state = { dateRange: null, - memberId: null, + memberId: props.match.params.memberId, }; } @@ -52,7 +52,7 @@ class PracticeSummaryReport extends Component { - + diff --git a/controllers/doorLock.js b/controllers/doorLock.js index 6e9626f..c34248a 100644 --- a/controllers/doorLock.js +++ b/controllers/doorLock.js @@ -27,7 +27,13 @@ const uploadDoorLockData = (req, res) => { parserResults.forEach((parserResult) => { parsedData.push(...parserResult.parsedData); parserErrors.push(...parserResult.errors); - unknownMembers.push(...parserResult.unknownMembers); + + parserResult.unknownMembers.forEach((newUnknownMember) => { + // Check if member is already labeled as unknown in different file + if (!unknownMembers.find((unknownMember) => unknownMember.details === newUnknownMember.details)){ + unknownMembers.push(newUnknownMember); + } + }); }); const asyncWriteJobs = []; diff --git a/services/doorLock/doorLock.js b/services/doorLock/doorLock.js index c59c6e7..803401e 100644 --- a/services/doorLock/doorLock.js +++ b/services/doorLock/doorLock.js @@ -3,10 +3,11 @@ const db = require('../../models'); const fs = require('fs'); const csv = require('csv-parser'); -const moment = require('moment/moment'); +const moment = require('moment-timezone'); const Op = require('sequelize').Op; const { + UI_TIMEZONE, USER_ENTRY_EVENT, ENABLE_PASSAGE_MODE, DISABLE_PASSAGE_MODE, @@ -38,7 +39,7 @@ const parseDoorLockDataFile = (file) => { return new Promise ((resolve, reject) => { const results = []; const errors = []; - const unknownMembers = []; + const unknownMembersToReport = []; let isValidFile = true; const prefetchDataJobs = [getMappingsFromDatabase(), fetchAllMembers()]; @@ -48,6 +49,11 @@ const parseDoorLockDataFile = (file) => { const mappings = result[0]; const allMembers = result[1]; + const membersMap = {}; + const unknownMembersMap = {}; + + allMembers.forEach((member) => membersMap[member.name] = member); + const mappingFromFileName = extractMappingFromFileName(file.name); const mappingObject = checkIfMappingExsists(mappingFromFileName, mappings); if (!mappingObject){ @@ -104,13 +110,15 @@ const parseDoorLockDataFile = (file) => { const secondEntry = results[i+1]; if (firstEntry && (firstEntry.event === USER_ENTRY_EVENT)){ - const memberObject = allMembers.find(member => member.name === firstEntry.name); + const memberObject = membersMap[firstEntry.name]; if (!memberObject){ //Check if member is already labeled as unknown - const unknownMember = unknownMembers.find((member) => member.details === firstEntry.name); + const unknownMember = unknownMembersMap[firstEntry.name]; + if (!unknownMember){ - unknownMembers.push({ + unknownMembersMap[firstEntry.name] = firstEntry.name; + unknownMembersToReport.push({ error: csvParserErrors.UNKNOWN_MEMBER, details: firstEntry.name, file: file.name, @@ -123,7 +131,7 @@ const parseDoorLockDataFile = (file) => { doorLockEvents.USER_UNLOCKED : doorLockEvents.USER_LOCKED; const dateTimeString = `${firstEntry.date} ${firstEntry.time}`; - const timestamp = moment.utc(dateTimeString, 'MM/DD/YY HH:mm:ss A').toISOString(); + const timestamp = moment.tz(dateTimeString, 'MM/DD/YY HH:mm:ss A', UI_TIMEZONE).tz('UTC').toISOString(); //Verify that member is registered in OfficeRnD system if (memberObject){ @@ -159,7 +167,7 @@ const parseDoorLockDataFile = (file) => { } resolve({ parsedData, - unknownMembers, + unknownMembers: unknownMembersToReport, errors }); }); diff --git a/services/integration/doorLockCharges.js b/services/integration/doorLockCharges.js index 9ce9228..2a8903e 100644 --- a/services/integration/doorLockCharges.js +++ b/services/integration/doorLockCharges.js @@ -21,160 +21,6 @@ const getSortedIncidentsForMember = (memberId) => { }) }; -const createUnlockedIncident = (reservation) => { - return new Promise((resolve, reject) => { - const { reservationId, memberId, resourceId, start, end } = reservation; - - getLastIncidentForMember(memberId) - .then(incidents => { - const lastIncident = incidents && incidents[0] ? incidents[0] : undefined; - - const incident = { - reservationId, - memberId, - resourceId, - bookingStart: start, - bookingEnd: end, - incidentLevel: null, - incidentLevelPrice: null, - }; - - console.log('=> UNLOCKED INCIDENT'); - console.log('\tMember : ', memberId); - console.log('\tStart : ', start); - console.log('\tEnd : ', end); - console.log('\tMore details : '); - - /* - if (lastIncident){ - const lastIncidentLevel = lastIncident.incidentLevel; - const lastIncidentBeginningOfTheMonth = moment(lastIncident.bookingStart).startOf('month'); - const beginningOfTheMonth = moment.utc().startOf('month'); - - const timePassedFromLastIncident = Math.abs(beginningOfTheMonth.diff(lastIncidentBeginningOfTheMonth, 'months')); - - if (timePassedFromLastIncident >= 6){ - console.log('\t\t-> This is first incident for this member in last 6 months'); - incident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title; - incident.incidentLevelPrice = unlockedIncidentLevelsPrices.UNLOCKED_0.price; - } else { - console.log('\t\t-> This member had incident(s) in past 6 months !!!'); - incident.incidentLevel = lastIncidentLevel; - incident.incidentLevelPrice = unlockedIncidentLevelsPrices[lastIncidentLevel].price; - } - console.log('\t\tLast incident details : '); - console.log('\t\tStart : ', lastIncident.bookingStart); - console.log('\t\tCalculated diff : ', timePassedFromLastIncident); - console.log('\t\t------------------'); - console.log('\tNew incident level : ', incident.incidentLevel); - } else { - console.log('\t\tThis is first incident for this member, EVER !'); - incident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title; - incident.incidentLevelPrice = unlockedIncidentLevelsPrices.UNLOCKED_0.price; - } - */ - - db.unlockedIncident.findOrCreate({ - where: { - reservationId, - memberId, - resourceId, - bookingStart: start, - bookingEnd: end, - }, - defaults: { - ...incident - } - }) - .then(()=>resolve()) - .catch((error)=>reject(error)); - }) - .catch((error) => { - reject(error); - }); - }); -}; - -const createUnscheduledUseIncident = (reservation, doorLockEntry) => { - return new Promise((resolve, reject) => { - const timeResolution = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION) || 5; - const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE) || 5; - - const reservationEndTime = moment(reservation.end); - const lockedTime = moment(doorLockEntry.timestamp); - const timeDifference = Math.abs(reservationEndTime.diff(lockedTime, 'minutes')); - - const timeIntervalsToCharge = Math.floor(timeDifference / timeResolution); - const totalChargeFee = timeIntervalsToCharge * chargePrice; - - if (timeIntervalsToCharge > 0){ - const incident = { - reservationId: reservation.reservationId, - memberId: reservation.memberId, - resourceId: reservation.resourceId, - bookingStart: reservation.start, - bookingEnd: reservation.end, - doorLockEventTimestamp: doorLockEntry.timestamp, - doorLockEventType: doorLockEntry.event, - chargePrice, - timeIntervalsToCharge, - totalChargeFee, - }; - - db.unscheduledIncident.findOrCreate({where: {...incident}, defaults: {...incident}}) - .then(()=>resolve()) - .catch((error)=>reject(error)); - }else{ - resolve(); - } - }); -}; - -const createDoorLockIncident = (reservation, doorLockEntry) => { - return new Promise((resolve, reject) => { - if (!doorLockEntry){ - // Check if there is unlock entry for this reservation - getUnlockEntryForReservation(reservation) - .then((unlockEntry) => { - if (!unlockEntry){ - // check if there is back-to-back booking before current one - getFirstPreviousBooking(reservation) - .then((previousReservation) => { - if (previousReservation){ - const previousReservationEnd = moment(previousReservation.end); - const currentReservationStart = moment(reservation.start); - const timeDifference = Math.abs(currentReservationStart.diff(previousReservationEnd, 'minutes')); - - const maxBackToBackDifference = parseInt(process.env.MAX_BACK_TO_BACK_DIFFERENCE) || 0; - if (timeDifference <= maxBackToBackDifference) { - createUnlockedIncident(reservation) - .then(() => resolve()) - .catch((error) => reject(error)); - }else{ - resolve(); - } - }else{ - resolve(); - } - }) - .catch((error)=>reject(error)); - }else { - createUnlockedIncident(reservation) - .then(()=>resolve()) - .catch((error)=>reject(error)); - } - }) - .catch((error) => { - reject(error); - }); - }else{ - createUnscheduledUseIncident(reservation, doorLockEntry) - .then(()=>resolve()) - .catch((error) => reject(error)); - } - }); -}; - const insertUnscheduledIncidents = (incidents) => { const asyncJobs = []; incidents.forEach((incident) => { @@ -333,8 +179,8 @@ const getIncidentData = (reservation) => { if (nextReservation){ // Check if next reservations is immediately after (back to back reservation) // If yes, then there is no need to check door lock entries related to this booking - const firstReservationEnd = moment(reservation.end); - const secondReservationStart = moment(nextReservation.start); + const firstReservationEnd = moment.utc(reservation.end); + const secondReservationStart = moment.utc(nextReservation.start); const timeDifference = Math.abs(secondReservationStart.diff(firstReservationEnd, 'minutes')); const maxBackToBackDifference = parseInt(process.env.MAX_BACK_TO_BACK_DIFFERENCE) || 0; @@ -355,8 +201,8 @@ const getIncidentData = (reservation) => { const timeResolution = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION) || 5 const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE) || 5; - const reservationEndTime = moment(reservation.end); - const lockedTime = moment(lockEntry.timestamp); + const reservationEndTime = moment.utc(reservation.end); + const lockedTime = moment.utc(lockEntry.timestamp); const timeDifference = Math.abs(reservationEndTime.diff(lockedTime, 'minutes')); const timeIntervalsToCharge = Math.floor(timeDifference / timeResolution); @@ -391,8 +237,8 @@ const getIncidentData = (reservation) => { getFirstPreviousBooking(reservation) .then((previousReservation) => { if (previousReservation){ - const previousReservationEnd = moment(previousReservation.end); - const currentReservationStart = moment(reservation.start); + const previousReservationEnd = moment.utc(previousReservation.end); + const currentReservationStart = moment.utc(reservation.start); const timeDifference = Math.abs(currentReservationStart.diff(previousReservationEnd, 'minutes')); const maxBackToBackDifference = parseInt(process.env.MAX_BACK_TO_BACK_DIFFERENCE) || 0; diff --git a/services/officeRnD/bookings.js b/services/officeRnD/bookings.js index e63d3d3..db98a88 100644 --- a/services/officeRnD/bookings.js +++ b/services/officeRnD/bookings.js @@ -41,7 +41,7 @@ const getAllFinishedBookings = () => { const filters = { canceled: false, end: { - [Op.lt]: moment().toISOString() + [Op.lt]: moment.utc().toISOString() } }; @@ -56,8 +56,8 @@ const getAllFinishedBookings = () => { const getFirstNextBooking = (reservation) => { return new Promise ((resolve, reject) => { - const {resourceId, start, timezone} = reservation; - const endOfTheDay = moment.tz(start, timezone).endOf('Day').toISOString(); + const { resourceId, start } = reservation; + const endOfTheDay = moment.utc(start).endOf('Day').toISOString(); const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone']; const filters = { @@ -90,8 +90,8 @@ const getFirstNextBooking = (reservation) => { const getFirstPreviousBooking = (reservation) => { return new Promise ((resolve, reject) => { - const {resourceId, start, timezone} = reservation; - const startOfTheDay = moment.tz(start, timezone).startOf('Day').toISOString(); + const { resourceId, start } = reservation; + const startOfTheDay = moment.utc(start).startOf('Day').toISOString(); const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone']; const filters = {