2020-06-02 14:17:17 +00:00
|
|
|
import React, { useState, useEffect } from 'react';
|
2020-06-02 23:11:34 +00:00
|
|
|
import {
|
|
|
|
Panel,
|
|
|
|
Form,
|
|
|
|
FormControl,
|
|
|
|
ControlLabel,
|
|
|
|
HelpBlock,
|
|
|
|
Button,
|
|
|
|
ButtonGroup,
|
|
|
|
Message,
|
|
|
|
FormGroup,
|
|
|
|
} from 'rsuite';
|
2020-06-02 14:17:17 +00:00
|
|
|
import { DateTime } from 'luxon';
|
2020-08-22 20:49:03 +00:00
|
|
|
import { useHistory } from 'react-router-dom';
|
2020-05-27 13:51:41 +00:00
|
|
|
|
2020-08-21 23:08:18 +00:00
|
|
|
import {
|
|
|
|
NavBar,
|
|
|
|
TimezonePicker,
|
|
|
|
DaySelector,
|
|
|
|
DurationSelector,
|
|
|
|
SelectedDates,
|
|
|
|
} from '../components';
|
2020-08-22 20:49:03 +00:00
|
|
|
import { backend } from '../helpers/http-common';
|
|
|
|
import { durations } from '../assets/data/durations';
|
2020-08-21 23:08:18 +00:00
|
|
|
import './styles/Schedule.less';
|
2020-06-02 23:11:34 +00:00
|
|
|
|
2020-08-22 20:49:03 +00:00
|
|
|
export default function Schedule({
|
|
|
|
possibleDates,
|
|
|
|
setPossibleDates,
|
|
|
|
currentMeeting,
|
|
|
|
setCurrentMeeting,
|
|
|
|
}) {
|
2020-06-02 14:17:17 +00:00
|
|
|
const [eventsList, setEventsList] = useState([]);
|
|
|
|
const [datesList, setDatesList] = useState(eventsToDates(eventsList));
|
2020-08-22 20:49:03 +00:00
|
|
|
const [durationIdx, setDurationIdx] = useState(0);
|
|
|
|
const [error, setError] = useState(false);
|
|
|
|
|
|
|
|
const history = useHistory();
|
2020-06-02 14:17:17 +00:00
|
|
|
|
2020-08-22 20:49:03 +00:00
|
|
|
useEffect(() => {
|
|
|
|
// Push the selected dates up the state tree
|
|
|
|
// but keep all related logic here
|
2020-08-22 22:28:05 +00:00
|
|
|
setPossibleDates(eventsList);
|
|
|
|
}, [eventsList, setPossibleDates]);
|
2020-08-22 20:49:03 +00:00
|
|
|
|
2020-06-02 14:17:17 +00:00
|
|
|
useEffect(() => {
|
2020-06-02 14:40:08 +00:00
|
|
|
let updatedDates = eventsToDates(eventsList).sort();
|
2020-06-02 14:17:17 +00:00
|
|
|
setDatesList(updatedDates);
|
|
|
|
}, [eventsList]);
|
|
|
|
|
2020-08-22 22:28:05 +00:00
|
|
|
// EVENTS & DATES
|
2020-08-22 20:49:03 +00:00
|
|
|
const handleSelectDates = (info) => {
|
2020-06-02 14:17:17 +00:00
|
|
|
let updatedEvents = [];
|
|
|
|
let datesList = new Set();
|
|
|
|
|
|
|
|
/// Add each date selected to datesList
|
|
|
|
let start = DateTime.fromISO(info.startStr);
|
|
|
|
let end = DateTime.fromISO(info.endStr);
|
|
|
|
|
|
|
|
// Check if selection contains only one day
|
|
|
|
let singleDay = end.toISODate() === start.plus({ days: 1 }).toISODate();
|
|
|
|
|
|
|
|
if (singleDay) {
|
|
|
|
// When a single date is selected
|
|
|
|
let selectedDate = info.startStr;
|
|
|
|
let selectedEvent = { start: selectedDate, display: 'background' };
|
|
|
|
|
|
|
|
// If selectedEvent exists in the list, find and set its index
|
|
|
|
let selectedEventIndex;
|
|
|
|
for (let index = 0; index < eventsList.length; index++) {
|
|
|
|
const eventDate = eventsList[index].start;
|
|
|
|
if (selectedDate === eventDate) {
|
|
|
|
// If it's the case, set current index to selectedEventIndex
|
|
|
|
selectedEventIndex = index;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selectedEventIndex !== undefined) {
|
|
|
|
// If selectedEventIndex exists, remove the corresponding event from the list
|
|
|
|
eventsList.splice(selectedEventIndex, 1);
|
|
|
|
updatedEvents = [...eventsList];
|
|
|
|
} else {
|
|
|
|
// Add selected event to the list
|
|
|
|
updatedEvents = [...eventsList, selectedEvent];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// When a range of dates is selected
|
|
|
|
let currentDate = start;
|
|
|
|
while (!(+end === +currentDate)) {
|
|
|
|
// Add currentDate to the datesList
|
|
|
|
datesList.add(currentDate.toISODate());
|
|
|
|
let newDate = currentDate.plus({ days: 1 });
|
|
|
|
currentDate = newDate;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add each date from the eventsList to datesList
|
|
|
|
eventsList.forEach((event) => {
|
|
|
|
datesList.add(event.start);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Create events from datesList and add them to updatedEvents
|
|
|
|
datesList.forEach((date) => {
|
|
|
|
updatedEvents.push({ start: date, display: 'background' });
|
|
|
|
});
|
|
|
|
}
|
|
|
|
setEventsList(updatedEvents);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDelete = (date) => {
|
|
|
|
let currentEvent = {
|
|
|
|
start: date.toFormat('yyyy-MM-dd'),
|
|
|
|
display: 'background',
|
|
|
|
};
|
|
|
|
|
|
|
|
// Find the event corresponding to the date
|
|
|
|
let selectedEventIndex;
|
|
|
|
for (let index = 0; index < eventsList.length; index++) {
|
|
|
|
const eventDate = eventsList[index].start;
|
|
|
|
if (currentEvent.start === eventDate) {
|
|
|
|
// When it's the case, set current index to selectedEventIndex
|
|
|
|
selectedEventIndex = index;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Create updated eventsList
|
|
|
|
eventsList.splice(selectedEventIndex, 1);
|
|
|
|
let updatedEvents = [...eventsList];
|
|
|
|
// Update the eventsList
|
|
|
|
setEventsList(updatedEvents);
|
|
|
|
};
|
|
|
|
|
2020-08-22 20:49:03 +00:00
|
|
|
const handleClear = () => {
|
|
|
|
setEventsList([]);
|
|
|
|
};
|
|
|
|
|
|
|
|
// MEETING
|
|
|
|
const handleSchedule = () => {
|
|
|
|
backend
|
|
|
|
.post('/meetings', currentMeeting)
|
|
|
|
.then((response) => {
|
|
|
|
history.push('/availability');
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
setError('Failed to add new account.');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleSelectTimezone = (value, item, event) => {
|
|
|
|
setCurrentMeeting({
|
|
|
|
...currentMeeting,
|
|
|
|
timezone: item.timezone,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleClean = (event) => {
|
|
|
|
setCurrentMeeting({
|
|
|
|
...currentMeeting,
|
|
|
|
timezone: '',
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleChangeMeeting = (value, evt) => {
|
|
|
|
setCurrentMeeting({
|
|
|
|
...currentMeeting,
|
|
|
|
[evt.target.name]: value,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleIncrement = () => {
|
|
|
|
if (durationIdx <= durations.length - 2) {
|
|
|
|
setDurationIdx(durationIdx + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentMeeting({
|
|
|
|
...currentMeeting,
|
|
|
|
duration: durations[durationIdx].duration,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDecrement = () => {
|
|
|
|
if (durationIdx > 0) {
|
|
|
|
setDurationIdx(durationIdx - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentMeeting({
|
|
|
|
...currentMeeting,
|
|
|
|
duration: durations[durationIdx].duration,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-05-27 13:51:41 +00:00
|
|
|
return (
|
2020-06-02 23:11:34 +00:00
|
|
|
<>
|
2020-08-19 18:04:52 +00:00
|
|
|
<NavBar title='Schedule a meeting' />
|
2020-08-21 23:08:18 +00:00
|
|
|
<Panel className={'app-container'}>
|
2020-06-02 23:11:34 +00:00
|
|
|
<Form className={'meeting-container'}>
|
|
|
|
<div className={'meeting-info'}>
|
|
|
|
<FormGroup>
|
|
|
|
<ControlLabel>Title</ControlLabel>
|
2020-08-22 20:49:03 +00:00
|
|
|
<FormControl
|
|
|
|
name='title'
|
|
|
|
type='text'
|
|
|
|
formValue={currentMeeting.title}
|
|
|
|
onChange={handleChangeMeeting}
|
|
|
|
/>
|
2020-06-02 23:11:34 +00:00
|
|
|
<HelpBlock>This field is required</HelpBlock>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
|
|
<ControlLabel>Description</ControlLabel>
|
2020-08-22 20:49:03 +00:00
|
|
|
<FormControl
|
2020-06-02 23:11:34 +00:00
|
|
|
name='description'
|
|
|
|
componentClass='textarea'
|
|
|
|
type='text'
|
|
|
|
rows={3}
|
|
|
|
placeholder='(optional)'
|
2020-08-22 20:49:03 +00:00
|
|
|
formValue={currentMeeting.description}
|
|
|
|
onChange={handleChangeMeeting}
|
2020-06-02 23:11:34 +00:00
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<div className='meeting-options-inline'>
|
|
|
|
<FormGroup className='meeting-timezone'>
|
|
|
|
<ControlLabel>Timezone</ControlLabel>
|
2020-08-22 20:49:03 +00:00
|
|
|
<TimezonePicker
|
|
|
|
handleSelect={handleSelectTimezone}
|
|
|
|
handleClean={handleClean}
|
|
|
|
timezone={currentMeeting.timezone}
|
|
|
|
/>
|
2020-06-02 23:11:34 +00:00
|
|
|
</FormGroup>
|
|
|
|
<FormGroup className='meeting-duration'>
|
|
|
|
<ControlLabel>Duration</ControlLabel>
|
2020-08-22 20:49:03 +00:00
|
|
|
<DurationSelector
|
|
|
|
durationIdx={durationIdx}
|
|
|
|
handleDecrement={handleDecrement}
|
|
|
|
handleIncrement={handleIncrement}
|
|
|
|
/>
|
2020-06-02 23:11:34 +00:00
|
|
|
</FormGroup>
|
|
|
|
</div>
|
|
|
|
<ButtonGroup justified>
|
|
|
|
<Button
|
|
|
|
appearance='ghost'
|
|
|
|
block
|
|
|
|
size='lg'
|
|
|
|
disabled={datesList.length === 0}
|
|
|
|
onClick={() => handleClear()}
|
|
|
|
>
|
|
|
|
Clear selection
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
appearance='primary'
|
|
|
|
size='lg'
|
|
|
|
block
|
|
|
|
disabled={datesList.length === 0}
|
2020-08-22 20:49:03 +00:00
|
|
|
onClick={handleSchedule}
|
2020-06-02 23:11:34 +00:00
|
|
|
>
|
|
|
|
Confirm dates
|
|
|
|
</Button>
|
2020-08-22 20:49:03 +00:00
|
|
|
{error && (
|
|
|
|
<Message type='error' description={error} />
|
|
|
|
)}
|
2020-06-02 23:11:34 +00:00
|
|
|
</ButtonGroup>
|
|
|
|
<div className={'selected-dates'}></div>
|
|
|
|
{datesList.length > 0 && (
|
|
|
|
<>
|
|
|
|
<SelectedDates
|
|
|
|
datesList={datesList}
|
|
|
|
handleDelete={handleDelete}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div className={'day-selector'}>
|
|
|
|
<Message
|
|
|
|
showIcon
|
|
|
|
type='info'
|
|
|
|
description='Select possible meetings dates on the calendar.'
|
|
|
|
/>
|
|
|
|
<DaySelector
|
|
|
|
eventsList={eventsList}
|
2020-08-22 20:49:03 +00:00
|
|
|
handleSelect={handleSelectDates}
|
2020-06-02 23:11:34 +00:00
|
|
|
handleClear={handleClear}
|
2020-06-02 14:17:17 +00:00
|
|
|
/>
|
2020-06-02 23:11:34 +00:00
|
|
|
</div>
|
|
|
|
</Form>
|
|
|
|
</Panel>
|
|
|
|
</>
|
2020-05-27 13:51:41 +00:00
|
|
|
);
|
|
|
|
}
|
2020-06-02 14:17:17 +00:00
|
|
|
|
|
|
|
// Convert events to Luxon Datetime objects
|
|
|
|
const eventsToDates = (events) => {
|
|
|
|
let dates = [];
|
|
|
|
events.forEach((event) => {
|
|
|
|
dates.push(DateTime.fromFormat(event.start, 'yyyy-MM-dd'));
|
|
|
|
});
|
|
|
|
return dates;
|
|
|
|
};
|