Fixed bookings and timezone problems
This commit is contained in:
@@ -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 = (
|
||||
<Grid.Column width={inlineButton ? 1 : null}>
|
||||
{ inlineButton && <label>{'\u00A0'}</label> }
|
||||
<Form.Button onClick={this.onButtonClick.bind(this)}>{buttonLabel}</Form.Button>
|
||||
</Grid.Column>
|
||||
);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Grid columns="equal">
|
||||
<Grid stackable columns={inlineButton ? null : 'equal'}>
|
||||
<Grid.Row>
|
||||
<Grid.Column>
|
||||
<Grid.Column width={inlineButton ? 7 : null}>
|
||||
<label>{startDateLabel}</label>
|
||||
<Form.Input
|
||||
fluid
|
||||
@@ -93,7 +101,7 @@ class DateRangePicker extends Component {
|
||||
onChange={this.onStartDateChange.bind(this)}
|
||||
/>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Grid.Column width={inlineButton ? 6 : null}>
|
||||
<label>{endDateLabel}</label>
|
||||
<Form.Input
|
||||
fluid
|
||||
@@ -103,6 +111,9 @@ class DateRangePicker extends Component {
|
||||
onChange={this.onEndDateChange.bind(this)}
|
||||
/>
|
||||
</Grid.Column>
|
||||
{
|
||||
inlineButton && buttonRender
|
||||
}
|
||||
</Grid.Row>
|
||||
{ error &&
|
||||
<Grid.Row>
|
||||
@@ -114,11 +125,12 @@ class DateRangePicker extends Component {
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
}
|
||||
<Grid.Row>
|
||||
<Grid.Column>
|
||||
<Form.Button onClick={this.onButtonClick.bind(this)}>{buttonLabel}</Form.Button>
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
{
|
||||
!inlineButton &&
|
||||
<Grid.Row>
|
||||
{buttonRender}
|
||||
</Grid.Row>
|
||||
}
|
||||
</Grid>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -7,14 +7,19 @@ import { mainMenuItems } from '../../constants/menuItems';
|
||||
const MainMenu = () =>
|
||||
(<Menu>
|
||||
{
|
||||
mainMenuItems.map(mainMenuItem =>
|
||||
<Menu.Item key={mainMenuItem.id}
|
||||
as={NavLink}
|
||||
to={mainMenuItem.url}
|
||||
exact
|
||||
>
|
||||
{mainMenuItem.title}
|
||||
</Menu.Item>)
|
||||
mainMenuItems.map(mainMenuItem => {
|
||||
if (!mainMenuItem.showInMenu){
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Menu.Item key={mainMenuItem.id}
|
||||
as={NavLink}
|
||||
to={mainMenuItem.url}
|
||||
exact
|
||||
>
|
||||
{mainMenuItem.title}
|
||||
</Menu.Item>)
|
||||
}})
|
||||
}
|
||||
</Menu>);
|
||||
|
||||
|
||||
@@ -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 <div style={{ textAlign: columnContentsAlignment }}>{cellValue}</div>
|
||||
if (openMemberSummaryOnMemberClick && urlValue){
|
||||
return <NavLink to={urlValue}>{cellValue}</NavLink>
|
||||
}else{
|
||||
return <div style={{ textAlign: columnContentsAlignment }}>{cellValue}</div>
|
||||
}
|
||||
|
||||
// return <NavLink to={urlValue}>
|
||||
// <div>{cellValue}</div>
|
||||
// </NavLink>
|
||||
|
||||
// return <div style={{ textAlign: columnContentsAlignment }}><a href={'www.gogole.com'} >{cellValue}</a></div>
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -24,7 +24,7 @@ class IncidentsReport extends Component {
|
||||
<hr/>
|
||||
<DateRangePicker buttonLabel="Show report" onDatesUpdate={this.onDatesUpdate.bind(this)} />
|
||||
<br/>
|
||||
<MemberIncidentsTable loading={pendingIncidents} incidents={incidents} />
|
||||
<MemberIncidentsTable loading={pendingIncidents} incidents={incidents} openMemberSummaryOnMemberClick />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
<MemberSelector onMemberSelect={this.onMemberSelectionUpdate.bind(this)} />
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<DateRangePicker onDatesUpdate={this.onDateRangeUpdate.bind(this)}/>
|
||||
<DateRangePicker inlineButton onDatesUpdate={this.onDateRangeUpdate.bind(this)}/>
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
<Grid.Row>
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user