Add Availability component

This commit is contained in:
rui hildt 2020-06-09 16:07:07 +02:00
parent fe1dafae3c
commit 036bbcb396
8 changed files with 277 additions and 55 deletions

View File

@ -8,6 +8,8 @@
"@fullcalendar/interaction": "^5.0.0-beta.4",
"@fullcalendar/luxon": "^4.4.2",
"@fullcalendar/react": "^5.0.0-beta.4",
"@fullcalendar/scrollgrid": "5.0.0-beta.4",
"@fullcalendar/timegrid": "5.0.0-beta.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",

View File

@ -1,9 +0,0 @@
import React from 'react'
export default function IntervalSelector() {
return (
<div>
It's empty for now
</div>
)
}

View File

@ -0,0 +1,153 @@
import React from 'react';
import { DateTime } from 'luxon';
import { Icon, IconButton } from 'rsuite';
import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interaction from '@fullcalendar/interaction';
import scrollGrid from '@fullcalendar/scrollgrid';
// FullCalendar props
const dayMinWidth = 110;
const slotMinTime = '08:00:00';
const slotMaxTime = '18:00:00';
export default function IntervalSelector({
selectedDates,
handleSelect,
availability,
setAvailability,
}) {
/// DATES DISPLAY
let startDate = selectedDates[0].start;
// create a Datetime object from the last event date,
// add one day, and change it to a date string
let lastEventDate = selectedDates[selectedDates.length - 1].start;
let endDate = DateTime.fromFormat(lastEventDate, 'yyyy-MM-dd')
.plus({ days: 1 })
.toFormat('yyyy-MM-dd');
// Create an event dates list used to create the days columns
const daysList = [];
selectedDates.forEach((event) => {
daysList.push(event.start);
});
const handleDayDidMount = (info) => {
let currentDate = DateTime.fromJSDate(info.date).toFormat('yyyy-MM-dd');
if (!daysList.includes(currentDate)) {
info.el.remove();
}
};
const handleViewMount = ({ el }) => {
// Adapt column width to number of dates present
const timegridBody = el.querySelectorAll('div.fc-timegrid-body');
const timegridSlots = el.querySelectorAll(
'div.fc-timegrid-slots table',
);
// Update width based on rendered dates number
let newWidth = daysList.length * dayMinWidth;
timegridBody[0].style['min-width'] = `${newWidth}px`;
timegridSlots[0].style['min-width'] = `${newWidth}px`;
};
/// AVAILABILITY INTERACTION
// Create an array of stringified availability
let avListJson = [];
availability.forEach((event) => {
avListJson.push(JSON.stringify(event));
});
const handleClick = (eventStart, eventEnd) => {
let updatedAvailibility = [];
// Create an event object to compare
let currentAvailability = {
start: eventStart,
end: eventEnd,
};
// Make it to JSON
let currentAvJson = JSON.stringify(currentAvailability);
// Check if there's a similar event obj in the availability
let index = avListJson.findIndex(
(availability) => currentAvJson === availability,
);
if (index > -1) {
updatedAvailibility = [...availability];
updatedAvailibility.splice(index, 1);
}
setAvailability([...updatedAvailibility]);
};
const handleContent = ({ event }) => {
let eventStart = event.start;
let eventEnd = event.end;
let evStartDT = DateTime.fromJSDate(eventStart);
let evEndDT = DateTime.fromJSDate(eventEnd);
return (
<>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'row',
}}
>
<p>
{evStartDT.toFormat('HH:mm')} -{' '}
{evEndDT.toFormat('HH:mm')}
</p>
<IconButton
className={'event-close'}
icon={<Icon icon='close' />}
appearance="default"
size="xs"
circle
onClick={() => handleClick(eventStart, eventEnd)}
></IconButton>
</div>
</>
);
};
return (
<FullCalendar
plugins={[dayGridPlugin, interaction, timeGridPlugin, scrollGrid]}
// timeZone={'UTC'}
events={availability}
// View props
headerToolbar={{ right: '' }}
initialView='timeGrid'
visibleRange={{
start: startDate,
end: endDate,
}}
allDaySlot={false}
dayMinWidth={dayMinWidth}
dayHeaderFormat={{
weekday: 'short',
day: 'numeric',
month: 'short',
omitCommas: true,
}}
slotMinTime={slotMinTime}
slotMaxTime={slotMaxTime}
dayCellDidMount={(arg) => handleDayDidMount(arg)}
dayHeaderDidMount={(arg) => handleDayDidMount(arg)}
viewDidMount={(arg) => handleViewMount(arg)}
// Interaction props
selectable={true}
unselectAuto={true}
selectOverlap={false}
select={(info) => handleSelect(info)}
longPressDelay={150}
eventContent={(arg) => handleContent(arg)}
/>
);
}

View File

@ -18,7 +18,7 @@ export default function MenuDropdown() {
<Dropdown.Item eventKey={'login'}>Login</Dropdown.Item>
<Dropdown.Item eventKey={'register'}>Register</Dropdown.Item>
<Dropdown.Item eventKey={'/'}>Dashboard</Dropdown.Item>
<Dropdown.Item eventKey={3}>Schedule a Meeting</Dropdown.Item>
<Dropdown.Item eventKey={'schedule'}>Schedule a Meeting</Dropdown.Item>
<Dropdown.Item eventKey={4}>Account Settings</Dropdown.Item>
<Dropdown.Item eventKey={5}>Log Out</Dropdown.Item>
</Dropdown.Menu>

View File

@ -9,18 +9,18 @@ export default function DaySelector({ eventsList, handleSelect, handleClear }) {
return (
<FullCalendar
plugins={[dayGridPlugin, interaction]}
initialView='dayGridMonth'
// showNonCurrentDates={false}
selectable={true}
// unselectAuto={false}
longPressDelay={150}
select={(info) => handleSelect(info)}
defaultAllDay={true}
events={eventsList}
// view props
initialView='dayGridMonth'
defaultAllDay={true}
customButtons={{
resetSelection: { text: 'clear selection', click: handleClear },
}}
headerToolbar={{ right: 'resetSelection today prev,next' }}
// interaction props
selectable={true}
longPressDelay={150}
select={(info) => handleSelect(info)}
/>
);
}

View File

@ -1,4 +1,5 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import {
Panel,
Form,
@ -9,43 +10,32 @@ import {
Message,
FormGroup,
} from 'rsuite';
import { DateTime } from 'luxon';
import NavBar from '../../components/Navbar/NavBar';
import TimezonePicker from '../../components/General/TimezonePicker';
import IntervalSelector from '../../components/Availability/IntervalSelector';
import IntervalSelector from '../../components/Interval/IntervalSelector';
import './Availability.less';
const eventsList = [
{
start: '2020-06-10',
display: 'background',
},
{
start: '2020-06-11',
display: 'background',
},
{
start: '2020-06-12',
display: 'background',
},
{
start: '2020-06-13',
display: 'background',
},
];
export default function Availability({ title }) {
// const [eventsList, setEventsList] = useState([]);
const [availability, setAvailability] = useState([]);
const [availability, setAvailability] = useState([...availabilityList]);
useEffect(() => {
console.log(availability);
}, [availability]);
const handleClear = () => {
setAvailability([]);
};
const handleSelect = () => {
console.log('Congrats, you have selected!');
const handleSelect = ({ start, end }) => {
let updatedEvents = [];
let newAvailability = {
start: start,
end: end,
};
updatedEvents.push(newAvailability);
setAvailability([...availability, ...updatedEvents]);
};
return (
@ -95,7 +85,9 @@ export default function Availability({ title }) {
description='Select your availibility on the calendar.'
/>
<IntervalSelector
eventsList={eventsList}
selectedDates={eventsList}
availability={availability}
setAvailability={setAvailability}
handleSelect={handleSelect}
handleClear={handleClear}
/>
@ -106,18 +98,76 @@ export default function Availability({ title }) {
);
}
// 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;
};
const containerStyle = {
// TODO Move to a .less file
maxWidth: '1200px',
margin: '30px auto',
backgroundColor: 'rgba(255,255,255,0.6)',
};
// TODO Remove fake data
const eventsList = [
{
start: '2020-06-05',
display: 'background',
},
{
start: '2020-06-06',
display: 'background',
},
{
start: '2020-06-08',
display: 'background',
},
{
start: '2020-06-10',
display: 'background',
},
{
start: '2020-06-11',
display: 'background',
},
{
start: '2020-06-21',
display: 'background',
},
{
start: '2020-06-29',
display: 'background',
},
];
const availabilityList = [
{
start: '2020-06-08T08:00:00.000Z',
end: '2020-06-08T12:00:00.000Z',
},
{
start: '2020-06-10T09:00:00.000Z',
end: '2020-06-10T13:00:00.000Z',
},
{
start: '2020-06-11T09:00:00.000Z',
end: '2020-06-11T13:00:00.000Z',
},
{
start: '2020-06-11T06:30:00.000Z',
end: '2020-06-11T08:30:00.000Z',
},
{
start: '2020-06-21T07:00:00.000Z',
end: '2020-06-21T11:30:00.000Z',
},
{
start: '2020-06-21T12:00:00.000Z',
end: '2020-06-21T15:30:00.000Z',
},
{
start: '2020-06-06T08:00:00.000Z',
end: '2020-06-06T14:00:00.000Z',
},
{
start: '2020-06-05T10:30:00.000Z',
end: '2020-06-05T14:30:00.000Z',
},
];

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Panel, Form, FormGroup, FormControl, ControlLabel, HelpBlock, Button } from 'rsuite';
import { Panel, Form, FormGroup, FormControl, ControlLabel, Button } from 'rsuite';
import NavBar from './../components/Navbar/NavBar';

View File

@ -1061,7 +1061,7 @@
preact "^10.0.5"
tslib "^1.9.3"
"@fullcalendar/daygrid@^5.0.0-beta.4":
"@fullcalendar/daygrid@5.0.0-beta.4", "@fullcalendar/daygrid@^5.0.0-beta.4":
version "5.0.0-beta.4"
resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-5.0.0-beta.4.tgz#b5616fddfe9a5496e5e98f20d6fafdcb6393a42e"
integrity sha512-ZWCF4dUCDngPqOJ3iUGl/ZiSOah8mgo9fayPR3WLazGMoQTsSAqMBGSuhFaNNdMDWQ+dhcT2JBa8e9DNoe2S+g==
@ -1082,6 +1082,14 @@
resolved "https://registry.yarnpkg.com/@fullcalendar/luxon/-/luxon-4.4.2.tgz#27b4913abd6fb323da8fda63e1bd03df96b21815"
integrity sha512-NQFPsdDAyk152JzsyNMrfZWpnZUTxCrP3to/0es8Eoa97i/UxjxB5+N0SVwJqcNO0rQpaLHmeg5piHhraABShQ==
"@fullcalendar/premium-common@5.0.0-beta.4":
version "5.0.0-beta.4"
resolved "https://registry.yarnpkg.com/@fullcalendar/premium-common/-/premium-common-5.0.0-beta.4.tgz#fd6571f2cba1026a9a42da8385c6e8febfdc7065"
integrity sha512-ZxmlzzjKAVy/xPP/HKkgxit0utPmHyjLw8Zj83KhmWHM7DRo9Eko0rNrSm/gDvsk0YkWpoiJRKcRRu022ulzLQ==
dependencies:
"@fullcalendar/common" "5.0.0-beta.4"
tslib "^1.9.3"
"@fullcalendar/react@^5.0.0-beta.4":
version "5.0.0-beta.4"
resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-5.0.0-beta.4.tgz#9c721093bfb49f938469f93960a10894623e152c"
@ -1090,6 +1098,24 @@
"@fullcalendar/common" "5.0.0-beta.4"
tslib "^1.9.3"
"@fullcalendar/scrollgrid@5.0.0-beta.4":
version "5.0.0-beta.4"
resolved "https://registry.yarnpkg.com/@fullcalendar/scrollgrid/-/scrollgrid-5.0.0-beta.4.tgz#e4651af5f44b0156aa838b8a292358da9897d41c"
integrity sha512-E9pkjqW5+BDWXo07SBaHei7VMNR5OozzRXFGDRMU1W/u+BzMP4IypBp0M8wDtL78IoJK3fNsv49vOWsHi4x7Mg==
dependencies:
"@fullcalendar/common" "5.0.0-beta.4"
"@fullcalendar/premium-common" "5.0.0-beta.4"
tslib "^1.9.3"
"@fullcalendar/timegrid@5.0.0-beta.4":
version "5.0.0-beta.4"
resolved "https://registry.yarnpkg.com/@fullcalendar/timegrid/-/timegrid-5.0.0-beta.4.tgz#e31b92772705738191617c9f1af4b91d27fcf8c6"
integrity sha512-kdI3NQ0Yt7bPxn0v6d01AyQeW5gKVPgGA8J2hsTpITkN/eaQJUxrmmOGEFhSVwvvJU0m+ji71Z30eKESNs/leA==
dependencies:
"@fullcalendar/common" "5.0.0-beta.4"
"@fullcalendar/daygrid" "5.0.0-beta.4"
tslib "^1.9.3"
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"