diff --git a/package.json b/package.json index bb53770..b506a8f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/Availability/IntervalSelector.js b/src/components/Availability/IntervalSelector.js deleted file mode 100644 index e169c7e..0000000 --- a/src/components/Availability/IntervalSelector.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -export default function IntervalSelector() { - return ( -
- It's empty for now -
- ) -} diff --git a/src/components/Interval/IntervalSelector.js b/src/components/Interval/IntervalSelector.js new file mode 100644 index 0000000..e9c8a70 --- /dev/null +++ b/src/components/Interval/IntervalSelector.js @@ -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 ( + <> +
+

+ {evStartDT.toFormat('HH:mm')} -{' '} + {evEndDT.toFormat('HH:mm')} +

+ } + appearance="default" + size="xs" + circle + onClick={() => handleClick(eventStart, eventEnd)} + > +
+ + ); + }; + + return ( + 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)} + /> + ); +} diff --git a/src/components/Navbar/MenuDropdown.js b/src/components/Navbar/MenuDropdown.js index 1aabcbe..14cc006 100644 --- a/src/components/Navbar/MenuDropdown.js +++ b/src/components/Navbar/MenuDropdown.js @@ -18,7 +18,7 @@ export default function MenuDropdown() { Login Register Dashboard - Schedule a Meeting + Schedule a Meeting Account Settings Log Out diff --git a/src/components/Schedule/DaySelector.js b/src/components/Schedule/DaySelector.js index 8f81eaa..cdfc222 100644 --- a/src/components/Schedule/DaySelector.js +++ b/src/components/Schedule/DaySelector.js @@ -9,18 +9,18 @@ export default function DaySelector({ eventsList, handleSelect, handleClear }) { return ( 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)} /> ); } diff --git a/src/screens/Availability/Availability.js b/src/screens/Availability/Availability.js index c115001..ee9a50f 100644 --- a/src/screens/Availability/Availability.js +++ b/src/screens/Availability/Availability.js @@ -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.' /> @@ -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', + }, +]; diff --git a/src/screens/Login.js b/src/screens/Login.js index d019808..a9d2952 100644 --- a/src/screens/Login.js +++ b/src/screens/Login.js @@ -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'; diff --git a/yarn.lock b/yarn.lock index 8fd6bcf..98444e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"