|
@@ -1,6 +1,19 @@
|
|
|
-import React, { useState, useEffect, useRef } from 'react';
|
|
|
+import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
|
import { getAvailability, setAvailability } from './api';
|
|
|
|
|
|
+
|
|
|
+// adapted from https://stackoverflow.com/a/60316951/13917490
|
|
|
+const START_DATE = new Date('05/30/2021');
|
|
|
+const END_DATE = new Date('09/01/2021');
|
|
|
+const DATES = Array.from(
|
|
|
+ { length: ((END_DATE - START_DATE) / 864e5) + 1 },
|
|
|
+ (_, i) => {
|
|
|
+ const date = new Date();
|
|
|
+ date.setDate(START_DATE.getDate()+i);
|
|
|
+ return { month: date.getMonth() + 1, day: date.getDate() };
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
const NameForm = ({ onNameSet }) => {
|
|
|
const [name, setName] = useState("");
|
|
|
|
|
@@ -38,12 +51,12 @@ const colors = {
|
|
|
maybe: "#ff0",
|
|
|
}
|
|
|
|
|
|
-const nextState = {
|
|
|
+const getNext = (user, availability) => ({
|
|
|
yes: "no",
|
|
|
no: "maybe",
|
|
|
maybe: "unknown",
|
|
|
unknown: "yes",
|
|
|
-}
|
|
|
+})[availability.find(({ name }) => name === user)?.status ?? "unknown"];
|
|
|
|
|
|
const pad = num => {
|
|
|
let snum = "" + num
|
|
@@ -53,7 +66,7 @@ const pad = num => {
|
|
|
|
|
|
const Tile = ({ user, month, day, availability, onClick }) => (
|
|
|
<div
|
|
|
- onClick={onClick}
|
|
|
+ onClick={() => onClick(month, day, getNext(user, availability))}
|
|
|
style={{
|
|
|
display: "flex",
|
|
|
flexFlow: "column nowrap",
|
|
@@ -94,53 +107,130 @@ const chunk = (arr, len) => {
|
|
|
return chunks;
|
|
|
}
|
|
|
|
|
|
-function App() {
|
|
|
- const [name, setName] = useState(null);
|
|
|
+const DaysOfWeek = () => (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ display: "flex",
|
|
|
+ flexFlow: "row nowrap",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {
|
|
|
+ ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
|
|
+ .map(day => (
|
|
|
+ <div
|
|
|
+ key={day}
|
|
|
+ style={{
|
|
|
+ width: "10em",
|
|
|
+ height: "1.5em",
|
|
|
+ border: "1px solid black",
|
|
|
+ margin: "2px 2px 2px 2px",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span style={{ paddingLeft: "0.5em" }}>{day}</span>
|
|
|
+ </div>
|
|
|
+ ))
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+)
|
|
|
+
|
|
|
+const Header = ({ name }) => (
|
|
|
+ <>
|
|
|
+ <span>Ignore how ugly this site is I wrote it in a total of 10 hours</span>
|
|
|
+ <span>Logged in as: {name}</span>
|
|
|
+ <span>Green is definitely available</span>
|
|
|
+ <span>Red is definitely not available</span>
|
|
|
+ <span>Yellow is possibly available</span>
|
|
|
+ <span>White is unknown availability</span>
|
|
|
+ <span>WEEK buttons toggle a whole week</span>
|
|
|
+ <DaysOfWeek />
|
|
|
+ </>
|
|
|
+)
|
|
|
+
|
|
|
+const Week = ({ days, user, change }) => (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ display: "flex",
|
|
|
+ flexFlow: "row nowrap",
|
|
|
+ alignItems: "center",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ display: "flex",
|
|
|
+ flexFlow: "row nowrap",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {
|
|
|
+ days.map(item => (
|
|
|
+ <Tile
|
|
|
+ key={item.day}
|
|
|
+ user={user}
|
|
|
+ month={item.month}
|
|
|
+ day={item.day}
|
|
|
+ availability={item.availability}
|
|
|
+ onClick={(month, day, status) => change(user, [{ month, day, status }])}
|
|
|
+ />
|
|
|
+ ))
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ onClick={() => change(user, days.map(({ month, day, availability }) => ({
|
|
|
+ month,
|
|
|
+ day,
|
|
|
+ status: getNext(user, availability),
|
|
|
+ })))}
|
|
|
+ style={{
|
|
|
+ paddingLeft: "1em",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ WEEK
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+)
|
|
|
+
|
|
|
+const useCalendar = () => {
|
|
|
const [calendar, setCalendar] = useState(null);
|
|
|
const lastUpdatedRef = useRef(null);
|
|
|
-
|
|
|
- const updateCalendar = async () => {
|
|
|
+ const updateCalendar = useCallback(async () => {
|
|
|
const { availability, lastUpdated } = await getAvailability();
|
|
|
if (lastUpdated !== lastUpdatedRef.current) {
|
|
|
// only actually update if there are changes
|
|
|
lastUpdatedRef.current = lastUpdated;
|
|
|
- setCalendar(availability);
|
|
|
+ const newCalendar = new Map();
|
|
|
+ availability.forEach(({ month, day, availability: availabilityList }) => {
|
|
|
+ newCalendar.set(`${month}-${day}`, availabilityList);
|
|
|
+ });
|
|
|
+ setCalendar(newCalendar);
|
|
|
}
|
|
|
- };
|
|
|
-
|
|
|
- const advance = async (dates) => {
|
|
|
- const updates = dates.map(({ month, day }) => {
|
|
|
- const currentStatus = calendar
|
|
|
- .find(item => item.month === month && item.day === day)
|
|
|
- ?.availability
|
|
|
- ?.find(item => item.name === name)
|
|
|
- ?.status
|
|
|
- ?? "unknown"
|
|
|
- const status = nextState[currentStatus] || "unknown";
|
|
|
- return { month, day, status }
|
|
|
- });
|
|
|
- const { lastUpdated, availability } = await setAvailability(name, updates);
|
|
|
- lastUpdatedRef.current = lastUpdated;
|
|
|
- setCalendar(availability);
|
|
|
- };
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- const interval = setInterval(updateCalendar, 5000); // update every few seconds
|
|
|
-
|
|
|
- updateCalendar(); // do an update to get started
|
|
|
-
|
|
|
- return () => clearInterval(interval); // cleanup if we unmount
|
|
|
- }, []);
|
|
|
-
|
|
|
-
|
|
|
- if (name === null) {
|
|
|
- return <NameForm onNameSet={setName} />
|
|
|
- }
|
|
|
+ }, [lastUpdatedRef]);
|
|
|
|
|
|
- if (calendar === null) {
|
|
|
- return <span>Loading...</span>
|
|
|
+ useEffect(() => {
|
|
|
+ // do the first update
|
|
|
+ updateCalendar();
|
|
|
+ // update every few seconds
|
|
|
+ const interval = setInterval(updateCalendar, 5000);
|
|
|
+ return () => { clearInterval(interval); };
|
|
|
+ }, [updateCalendar]);
|
|
|
+
|
|
|
+ const getter = (month, day) => calendar?.get(`${month}-${day}`) ?? [];
|
|
|
+
|
|
|
+ const change = useCallback(async (name, updates) => {
|
|
|
+ await setAvailability(name, updates);
|
|
|
+ await updateCalendar();
|
|
|
+ }, [updateCalendar]);
|
|
|
+
|
|
|
+ return [getter, change];
|
|
|
+}
|
|
|
+
|
|
|
+function App() {
|
|
|
+ const [name, setName] = useState(null);
|
|
|
+ const [getDay, change] = useCalendar();
|
|
|
+
|
|
|
+ if (name === null) {
|
|
|
+ return <NameForm onNameSet={setName} />;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
return (
|
|
|
<div
|
|
|
style={{
|
|
@@ -148,72 +238,16 @@ function App() {
|
|
|
flexFlow: "column nowrap",
|
|
|
}}
|
|
|
>
|
|
|
- <span>Ignore how ugly this site is I wrote it in a total of 10 hours</span>
|
|
|
- <span>Logged in as: {name}</span>
|
|
|
- <span>Green is definitely available</span>
|
|
|
- <span>Red is definitely not available</span>
|
|
|
- <span>Yellow is possibly available</span>
|
|
|
- <span>White is unknown availability</span>
|
|
|
- <span>WEEK buttons toggle a whole week</span>
|
|
|
- <div
|
|
|
- style={{
|
|
|
- display: "flex",
|
|
|
- flexFlow: "row nowrap",
|
|
|
- }}
|
|
|
- >
|
|
|
- {
|
|
|
- ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
|
|
- .map(day => (
|
|
|
- <div
|
|
|
- style={{
|
|
|
- width: "10em",
|
|
|
- height: "1.5em",
|
|
|
- border: "1px solid black",
|
|
|
- margin: "2px 2px 2px 2px",
|
|
|
- }}
|
|
|
- >
|
|
|
- <span style={{ paddingLeft: "0.5em" }}>{day}</span>
|
|
|
- </div>
|
|
|
- ))
|
|
|
- }
|
|
|
- </div>
|
|
|
+ <Header name={name} />
|
|
|
{
|
|
|
- chunk(calendar, 7).map((row, ind) => (
|
|
|
- <div
|
|
|
- key={ind}
|
|
|
- style={{
|
|
|
- display: "flex",
|
|
|
- flexFlow: "row nowrap",
|
|
|
- alignItems: "center",
|
|
|
- }}
|
|
|
- >
|
|
|
- <div
|
|
|
- style={{
|
|
|
- display: "flex",
|
|
|
- flexFlow: "row nowrap",
|
|
|
- }}
|
|
|
- >
|
|
|
- {
|
|
|
- row.map(item => (
|
|
|
- <Tile
|
|
|
- onClick={() => advance([item])}
|
|
|
- key={item.day}
|
|
|
- user={name}
|
|
|
- {...item}
|
|
|
- />
|
|
|
- ))
|
|
|
- }
|
|
|
- </div>
|
|
|
- <div
|
|
|
- onClick={() => advance(row)}
|
|
|
- style={{
|
|
|
- paddingLeft: "1em",
|
|
|
- }}
|
|
|
- >
|
|
|
- WEEK
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- ))
|
|
|
+ chunk(DATES, 7).map(days =>
|
|
|
+ <Week
|
|
|
+ key={`${days[0].month}-${days[0].day}`}
|
|
|
+ days={days.map(({ month, day }) => ({ month, day, availability: getDay(month, day) }))}
|
|
|
+ change={change}
|
|
|
+ user={name}
|
|
|
+ />
|
|
|
+ )
|
|
|
}
|
|
|
</div>
|
|
|
);
|