mirror of
https://github.com/mvvasilev/findtheti.me.git
synced 2025-04-19 13:39:52 +03:00
Optimize availability picker, use new approach for displaying available times
This commit is contained in:
parent
39ed7d8932
commit
fe50c21f84
6 changed files with 146 additions and 160 deletions
|
@ -1,7 +1,7 @@
|
||||||
import { Box, Stack } from "@mui/material";
|
import { Box, Stack } from "@mui/material";
|
||||||
import { MouseEvent, useState } from "react";
|
import { MouseEvent, useState } from "react";
|
||||||
import "./css/AvailabilityPicker.css";
|
import "./css/AvailabilityPicker.css";
|
||||||
import { AvailabilityDay, OthersDay, OthersDays } from "../types/Availabilities";
|
import { AvailabilityDay, UserAvailabilityHeatmap } from "../types/Availabilities";
|
||||||
import utils from "../utils";
|
import utils from "../utils";
|
||||||
import AvailabilityPickerDay from "./AvailabilityPickerDay";
|
import AvailabilityPickerDay from "./AvailabilityPickerDay";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
@ -25,7 +25,7 @@ type GhostPreviewProps = {
|
||||||
const AvailabilityPicker = (props: {
|
const AvailabilityPicker = (props: {
|
||||||
days: AvailabilityDay[],
|
days: AvailabilityDay[],
|
||||||
setDays: (days: AvailabilityDay[]) => void,
|
setDays: (days: AvailabilityDay[]) => void,
|
||||||
othersAvailabilities: OthersDays[],
|
availabilityHeatmap: UserAvailabilityHeatmap,
|
||||||
eventType: String,
|
eventType: String,
|
||||||
availabilityDurationInMinutes: number,
|
availabilityDurationInMinutes: number,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -64,6 +64,14 @@ const AvailabilityPicker = (props: {
|
||||||
|
|
||||||
day.availableTimes.splice(existingTime, 1);
|
day.availableTimes.splice(existingTime, 1);
|
||||||
|
|
||||||
|
let dayIndex = props.days.findIndex(d => d.forDate.unix() === day.forDate.unix());
|
||||||
|
|
||||||
|
if (dayIndex === undefined) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
props.days.splice(dayIndex, 1, {...day});
|
||||||
|
}
|
||||||
|
|
||||||
props.setDays([...props.days]);
|
props.setDays([...props.days]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,13 +89,14 @@ const AvailabilityPicker = (props: {
|
||||||
let existingTimeContainingFrom = day.availableTimes.findIndex(t => utils.dayjsIsBetweenUnixInclusive(t.fromTime, fromTime, t.toTime));
|
let existingTimeContainingFrom = day.availableTimes.findIndex(t => utils.dayjsIsBetweenUnixInclusive(t.fromTime, fromTime, t.toTime));
|
||||||
let existingTimeContainingTo = day.availableTimes.findIndex(t => utils.dayjsIsBetweenUnixInclusive(t.fromTime, toTime, t.toTime));
|
let existingTimeContainingTo = day.availableTimes.findIndex(t => utils.dayjsIsBetweenUnixInclusive(t.fromTime, toTime, t.toTime));
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
// the newly created availability crosses another single one. Both have the same from and to. Do nothing.
|
// the newly created availability crosses another single one. Both have the same from and to. Do nothing.
|
||||||
if (existingTimeContainingFrom >= 0 && existingTimeContainingTo >= 0 && existingTimeContainingFrom === existingTimeContainingTo) {
|
case (existingTimeContainingFrom >= 0 && existingTimeContainingTo >= 0 && existingTimeContainingFrom === existingTimeContainingTo): {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the newly created availability crosses 2 existing ones. Combine all of them into a single one.
|
// the newly created availability crosses 2 existing ones. Combine all of them into a single one.
|
||||||
if (existingTimeContainingFrom >= 0 && existingTimeContainingTo >= 0 && existingTimeContainingFrom !== existingTimeContainingTo) {
|
case (existingTimeContainingFrom >= 0 && existingTimeContainingTo >= 0 && existingTimeContainingFrom !== existingTimeContainingTo): {
|
||||||
|
|
||||||
let newFrom = day.availableTimes[existingTimeContainingFrom].fromTime;
|
let newFrom = day.availableTimes[existingTimeContainingFrom].fromTime;
|
||||||
let newTo = day.availableTimes[existingTimeContainingTo].toTime;
|
let newTo = day.availableTimes[existingTimeContainingTo].toTime;
|
||||||
|
|
||||||
|
@ -99,13 +108,11 @@ const AvailabilityPicker = (props: {
|
||||||
toTime: newTo
|
toTime: newTo
|
||||||
});
|
});
|
||||||
|
|
||||||
props.setDays([...props.days]);
|
break;
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The newly created availability from is within an existing one. Combine the 2 into one.
|
// The newly created availability from is within an existing one. Combine the 2 into one.
|
||||||
if (existingTimeContainingFrom >= 0 && existingTimeContainingTo < 0) {
|
case (existingTimeContainingFrom >= 0 && existingTimeContainingTo < 0): {
|
||||||
|
|
||||||
let newFrom = day.availableTimes[existingTimeContainingFrom].fromTime;
|
let newFrom = day.availableTimes[existingTimeContainingFrom].fromTime;
|
||||||
|
|
||||||
day.availableTimes.splice(existingTimeContainingFrom, 1);
|
day.availableTimes.splice(existingTimeContainingFrom, 1);
|
||||||
|
@ -115,13 +122,11 @@ const AvailabilityPicker = (props: {
|
||||||
toTime: toTime
|
toTime: toTime
|
||||||
});
|
});
|
||||||
|
|
||||||
props.setDays([...props.days]);
|
break;
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The newly created availability to is within an existing one. Combine the 2 into one.
|
// The newly created availability to is within an existing one. Combine the 2 into one.
|
||||||
if (existingTimeContainingFrom < 0 && existingTimeContainingTo >= 0) {
|
case (existingTimeContainingFrom < 0 && existingTimeContainingTo >= 0): {
|
||||||
|
|
||||||
let newTo = day.availableTimes[existingTimeContainingTo].toTime;
|
let newTo = day.availableTimes[existingTimeContainingTo].toTime;
|
||||||
|
|
||||||
day.availableTimes.splice(existingTimeContainingTo, 1);
|
day.availableTimes.splice(existingTimeContainingTo, 1);
|
||||||
|
@ -131,16 +136,26 @@ const AvailabilityPicker = (props: {
|
||||||
toTime: newTo
|
toTime: newTo
|
||||||
});
|
});
|
||||||
|
|
||||||
props.setDays([...props.days]);
|
break;
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
default: {
|
||||||
day.availableTimes.push({
|
day.availableTimes.push({
|
||||||
fromTime: fromTime,
|
fromTime: fromTime,
|
||||||
toTime: toTime
|
toTime: toTime
|
||||||
});
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dayIndex = props.days.findIndex(d => d.forDate.unix() === day.forDate.unix());
|
||||||
|
|
||||||
|
if (dayIndex === undefined) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
props.days.splice(dayIndex, 1, {...day});
|
||||||
|
}
|
||||||
|
|
||||||
props.setDays([...props.days]);
|
props.setDays([...props.days]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,13 +178,9 @@ const AvailabilityPicker = (props: {
|
||||||
key={day.forDate.unix()}
|
key={day.forDate.unix()}
|
||||||
day={day}
|
day={day}
|
||||||
eventType={props.eventType}
|
eventType={props.eventType}
|
||||||
|
currentTotalRespondents={props.availabilityHeatmap.maxNumberOfRespondents}
|
||||||
halfHourDisplayHeight={HALFHOUR_DISPLAY_HEIGHT}
|
halfHourDisplayHeight={HALFHOUR_DISPLAY_HEIGHT}
|
||||||
othersAvailabilityDay={props.othersAvailabilities.map(a => {
|
availabilityHeatmap={props.availabilityHeatmap}
|
||||||
return {
|
|
||||||
userName: a.userName,
|
|
||||||
availableTimes: a.days.find(d => d.forDate.unix() === day.forDate.unix())?.availableTimes ?? []
|
|
||||||
} as OthersDay;
|
|
||||||
})}
|
|
||||||
onMouseEnterHalfhour={(e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs) => {
|
onMouseEnterHalfhour={(e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs) => {
|
||||||
displayGhostPeriod(e, time);
|
displayGhostPeriod(e, time);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AvailabilityDay, OthersDay } from "../types/Availabilities";
|
import { AvailabilityDay, UserAvailabilityHeatmap } from "../types/Availabilities";
|
||||||
import utils from "../utils";
|
import utils from "../utils";
|
||||||
import { Box, Card, Divider, Typography } from "@mui/material";
|
import { Box, Card, Divider, Typography } from "@mui/material";
|
||||||
import { EventTypes } from "../types/Event";
|
import { EventTypes } from "../types/Event";
|
||||||
|
@ -8,6 +8,7 @@ import dayjs from "dayjs";
|
||||||
import utc from 'dayjs/plugin/utc';
|
import utc from 'dayjs/plugin/utc';
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
|
@ -17,12 +18,13 @@ const AvailabilityPickerDay = (props: {
|
||||||
day: AvailabilityDay,
|
day: AvailabilityDay,
|
||||||
eventType: String,
|
eventType: String,
|
||||||
halfHourDisplayHeight: number,
|
halfHourDisplayHeight: number,
|
||||||
othersAvailabilityDay: OthersDay[],
|
currentTotalRespondents: number,
|
||||||
|
availabilityHeatmap: UserAvailabilityHeatmap,
|
||||||
onMouseEnterHalfhour: (e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs) => void,
|
onMouseEnterHalfhour: (e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs) => void,
|
||||||
onMouseClickHalfhour: (day: AvailabilityDay, time: dayjs.Dayjs, isDelete: boolean) => void
|
onMouseClickHalfhour: (day: AvailabilityDay, time: dayjs.Dayjs, isDelete: boolean) => void
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const generateHours = (): JSX.Element[] => {
|
const generateHours = useMemo((): JSX.Element[] => {
|
||||||
let hours: JSX.Element[] = [];
|
let hours: JSX.Element[] = [];
|
||||||
|
|
||||||
for (var i = 0; i < 24; i++) {
|
for (var i = 0; i < 24; i++) {
|
||||||
|
@ -34,8 +36,9 @@ const AvailabilityPickerDay = (props: {
|
||||||
key={fullHourTime.unix()}
|
key={fullHourTime.unix()}
|
||||||
dateTime={fullHourTime}
|
dateTime={fullHourTime}
|
||||||
halfHourDisplayHeight={props.halfHourDisplayHeight}
|
halfHourDisplayHeight={props.halfHourDisplayHeight}
|
||||||
namesMarkedFullHourAsAvailable={props.othersAvailabilityDay.filter(d => d.availableTimes.some(t => utils.dayjsIsBetweenUnixExclusive(t.fromTime, fullHourTime, t.toTime))).map(d => d.userName)}
|
currentTotalRespondents={props.currentTotalRespondents}
|
||||||
namesMarkedHalfHourAsAvailable={props.othersAvailabilityDay.filter(d => d.availableTimes.some(t => utils.dayjsIsBetweenUnixExclusive(t.fromTime, halfHourTime, t.toTime))).map(d => d.userName)}
|
namesMarkedFullHourAsAvailable={props.availabilityHeatmap.getNamesAt(fullHourTime.unix())}
|
||||||
|
namesMarkedHalfHourAsAvailable={props.availabilityHeatmap.getNamesAt(fullHourTime.add(30, "minutes").unix())}
|
||||||
isFullHourSelected={props.day.availableTimes.some(a => utils.dayjsIsBetweenUnixExclusive(a.fromTime, fullHourTime, a.toTime))}
|
isFullHourSelected={props.day.availableTimes.some(a => utils.dayjsIsBetweenUnixExclusive(a.fromTime, fullHourTime, a.toTime))}
|
||||||
isHalfHourSelected={props.day.availableTimes.some(a => utils.dayjsIsBetweenUnixExclusive(a.fromTime, halfHourTime, a.toTime))}
|
isHalfHourSelected={props.day.availableTimes.some(a => utils.dayjsIsBetweenUnixExclusive(a.fromTime, halfHourTime, a.toTime))}
|
||||||
onMouseEnterHalfhour={(e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs): void => {
|
onMouseEnterHalfhour={(e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs): void => {
|
||||||
|
@ -49,7 +52,7 @@ const AvailabilityPickerDay = (props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
return hours;
|
return hours;
|
||||||
}
|
}, [props.day]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
@ -84,10 +87,10 @@ const AvailabilityPickerDay = (props: {
|
||||||
|
|
||||||
<Divider></Divider>
|
<Divider></Divider>
|
||||||
|
|
||||||
{generateHours()}
|
{generateHours}
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default AvailabilityPickerDay;
|
export default AvailabilityPickerDay;
|
|
@ -17,6 +17,7 @@ const AvailabilityPickerHour = (props: {
|
||||||
isFullHourSelected: boolean,
|
isFullHourSelected: boolean,
|
||||||
isHalfHourSelected: boolean,
|
isHalfHourSelected: boolean,
|
||||||
halfHourDisplayHeight: number,
|
halfHourDisplayHeight: number,
|
||||||
|
currentTotalRespondents: number,
|
||||||
namesMarkedFullHourAsAvailable: String[],
|
namesMarkedFullHourAsAvailable: String[],
|
||||||
namesMarkedHalfHourAsAvailable: String[],
|
namesMarkedHalfHourAsAvailable: String[],
|
||||||
onMouseEnterHalfhour: (e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: Dayjs) => void,
|
onMouseEnterHalfhour: (e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: Dayjs) => void,
|
||||||
|
@ -26,6 +27,15 @@ const AvailabilityPickerHour = (props: {
|
||||||
return `${names.length} ${names.length > 1 ? "people have" : "person has"} marked this time as available: ${names.join(", ")}`;
|
return `${names.length} ${names.length > 1 ? "people have" : "person has"} marked this time as available: ${names.join(", ")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const heatMapColorforValue = (value: number) => {
|
||||||
|
if (value === 0 || props.currentTotalRespondents === 0) {
|
||||||
|
return 'inherit';
|
||||||
|
}
|
||||||
|
|
||||||
|
var h = (1.0 - (value / props.currentTotalRespondents)) * 240
|
||||||
|
return "hsl(" + h + ", 75%, 35%)";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
//className={classNames({ "hour-light": isEvenHour, "hour-dark": !isEvenHour })}
|
//className={classNames({ "hour-light": isEvenHour, "hour-dark": !isEvenHour })}
|
||||||
|
@ -41,7 +51,8 @@ const AvailabilityPickerHour = (props: {
|
||||||
enterDelay={500}
|
enterDelay={500}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
className={classNames("full-hour", `hour-available-${props.namesMarkedFullHourAsAvailable.length}`, { "selected-availability": props.isFullHourSelected })}
|
className={classNames("full-hour", { "selected-availability": props.isFullHourSelected })}
|
||||||
|
bgcolor={heatMapColorforValue(props.namesMarkedFullHourAsAvailable.length)}
|
||||||
height={props.halfHourDisplayHeight}
|
height={props.halfHourDisplayHeight}
|
||||||
onMouseEnter={(e) => props.onMouseEnterHalfhour(e, props.dateTime)}
|
onMouseEnter={(e) => props.onMouseEnterHalfhour(e, props.dateTime)}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
|
@ -67,7 +78,8 @@ const AvailabilityPickerHour = (props: {
|
||||||
enterDelay={500}
|
enterDelay={500}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
className={classNames("half-hour", `hour-available-${props.namesMarkedHalfHourAsAvailable.length}`, { "selected-availability": props.isHalfHourSelected })}
|
className={classNames("half-hour", { "selected-availability": props.isHalfHourSelected })}
|
||||||
|
bgcolor={heatMapColorforValue(props.namesMarkedHalfHourAsAvailable.length)}
|
||||||
height={props.halfHourDisplayHeight}
|
height={props.halfHourDisplayHeight}
|
||||||
onMouseEnter={(e) => props.onMouseEnterHalfhour(e, props.dateTime.add(30, "minutes"))}
|
onMouseEnter={(e) => props.onMouseEnterHalfhour(e, props.dateTime.add(30, "minutes"))}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
|
|
|
@ -15,50 +15,6 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div[class*=" hour-available-"]{
|
|
||||||
background-color: #bb00bb;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-0 {
|
|
||||||
background-color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-1 {
|
|
||||||
background-color: #330033;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-2 {
|
|
||||||
background-color: #440044;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-3 {
|
|
||||||
background-color: #550055;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-4 {
|
|
||||||
background-color: #660066;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-5 {
|
|
||||||
background-color: #770077;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-6 {
|
|
||||||
background-color: #880088;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-7 {
|
|
||||||
background-color: #990099;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-8 {
|
|
||||||
background-color: #aa00aa;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-available-9 {
|
|
||||||
background-color: #bb00bb;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hour-light {
|
div.hour-light {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: solid 1px;
|
border-top: solid 1px;
|
||||||
|
@ -87,9 +43,9 @@ div.selected-availability {
|
||||||
background-color: var(--currently-selected-color);
|
background-color: var(--currently-selected-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.full-hour:hover, div.half-hour:hover {
|
/* div.full-hour:hover, div.half-hour:hover {
|
||||||
background-color: var(--hover-color);
|
background-color: var(--hover-color);
|
||||||
}
|
} */
|
||||||
|
|
||||||
div.full-hour:active, div.half-hour:active {
|
div.full-hour:active, div.half-hour:active {
|
||||||
background-color: var(--active-color);
|
background-color: var(--active-color);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import utc from 'dayjs/plugin/utc';
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
import duration from 'dayjs/plugin/duration';
|
import duration from 'dayjs/plugin/duration';
|
||||||
import { AvailabilityDay, AvailabilityTime, OthersDays } from "../types/Availabilities";
|
import { AvailabilityDay, UserAvailabilityHeatmap } from "../types/Availabilities";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
@ -26,12 +26,14 @@ export default function ExistingEventPage() {
|
||||||
const [canSubmit, setCanSubmit] = useState<boolean>(false);
|
const [canSubmit, setCanSubmit] = useState<boolean>(false);
|
||||||
const [event, setEvent] = useState<Event>(createEvent());
|
const [event, setEvent] = useState<Event>(createEvent());
|
||||||
const [days, setDays] = useState<AvailabilityDay[]>([]);
|
const [days, setDays] = useState<AvailabilityDay[]>([]);
|
||||||
const [othersDays, setOthersDays] = useState<OthersDays[]>([]);
|
const [availabilityHeatmap, setAvailabilityHeatmap] = useState<UserAvailabilityHeatmap | undefined>();
|
||||||
const [userName, setUserName] = useState<String | undefined>(undefined);
|
const [userName, setUserName] = useState<String | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
utils.showSpinner();
|
utils.showSpinner();
|
||||||
|
|
||||||
|
let localTimezone = dayjs.tz.guess();
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
utils.performRequest(`/api/events/${eventId}`)
|
utils.performRequest(`/api/events/${eventId}`)
|
||||||
.then(result => setEvent({
|
.then(result => setEvent({
|
||||||
|
@ -47,41 +49,23 @@ export default function ExistingEventPage() {
|
||||||
|
|
||||||
utils.performRequest(`/api/events/${eventId}/availabilities`)
|
utils.performRequest(`/api/events/${eventId}/availabilities`)
|
||||||
.then((result: [{ id: number, from_date: string, to_date: string, user_name: string }]) => {
|
.then((result: [{ id: number, from_date: string, to_date: string, user_name: string }]) => {
|
||||||
let othersDays: OthersDays[] = [];
|
let heatmap = new UserAvailabilityHeatmap();
|
||||||
let localTimezone = dayjs.tz.guess();
|
|
||||||
|
const LENGTH_OF_30_MINUTES_IN_SECONDS = 1800;
|
||||||
|
|
||||||
for (const availability of result) {
|
for (const availability of result) {
|
||||||
let fromDate = dayjs(availability.from_date).tz(localTimezone);
|
let start = dayjs(availability.from_date).tz(localTimezone);
|
||||||
let toDate = dayjs(availability.to_date).tz(localTimezone);
|
let end = dayjs(availability.to_date).tz(localTimezone);
|
||||||
|
|
||||||
var userTimes = othersDays.find(d => d.userName === availability.user_name);
|
let startUnix = start.unix();
|
||||||
|
let endUnix = end.unix();
|
||||||
|
|
||||||
if (!userTimes) {
|
for (var timeInUnix = startUnix; timeInUnix <= endUnix; timeInUnix += LENGTH_OF_30_MINUTES_IN_SECONDS) {
|
||||||
userTimes = { userName: availability.user_name, days: []} as OthersDays;
|
heatmap.addName(timeInUnix, availability.user_name);
|
||||||
|
}
|
||||||
othersDays.push(userTimes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let availabilityDay = fromDate.hour(0).minute(0).second(0).millisecond(0);
|
setAvailabilityHeatmap(heatmap);
|
||||||
|
|
||||||
var day = userTimes.days.find(d => d.forDate.unix() === availabilityDay.unix());
|
|
||||||
|
|
||||||
if (!day) {
|
|
||||||
day = {
|
|
||||||
forDate: availabilityDay,
|
|
||||||
availableTimes: []
|
|
||||||
} as AvailabilityDay;
|
|
||||||
|
|
||||||
userTimes.days.push(day);
|
|
||||||
}
|
|
||||||
|
|
||||||
day.availableTimes.push({
|
|
||||||
fromTime: fromDate,
|
|
||||||
toTime: toDate
|
|
||||||
} as AvailabilityTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
setOthersDays(othersDays);
|
|
||||||
})
|
})
|
||||||
.catch(e => toast.error(e))
|
.catch(e => toast.error(e))
|
||||||
])
|
])
|
||||||
|
@ -214,17 +198,17 @@ export default function ExistingEventPage() {
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid xs={12}>
|
<Grid xs={12}>
|
||||||
{
|
{
|
||||||
(event.fromDate !== null && event.toDate !== null && event.eventType !== null) &&
|
(event.fromDate !== null && event.toDate !== null && event.eventType !== null && availabilityHeatmap) &&
|
||||||
<AvailabilityPicker
|
<AvailabilityPicker
|
||||||
days={days}
|
days={days}
|
||||||
setDays={(days) => setDays(days)}
|
setDays={(days) => setDays(days)}
|
||||||
othersAvailabilities={othersDays}
|
availabilityHeatmap={availabilityHeatmap}
|
||||||
eventType={event.eventType}
|
eventType={event.eventType}
|
||||||
availabilityDurationInMinutes={event.duration}
|
availabilityDurationInMinutes={event.duration}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<Typography pt={1} fontSize={"0.65em"}>
|
<Typography pt={1} fontSize={"0.65em"}>
|
||||||
Left-click to select when you're available, right-click to remove the highlighted hours.
|
Date and times are in your local timezone. Left-click to select when you're available, right-click to remove the highlighted hours.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid xs={0} md={3}></Grid>
|
<Grid xs={0} md={3}></Grid>
|
||||||
|
|
|
@ -10,12 +10,32 @@ export type AvailabilityDay = {
|
||||||
availableTimes: AvailabilityTime[]
|
availableTimes: AvailabilityTime[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OthersDays = {
|
export type UserAvailabilityHeatmapValue = {
|
||||||
userName: String,
|
usersAvailableAtTime: String[]
|
||||||
days: AvailabilityDay[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OthersDay = {
|
export class UserAvailabilityHeatmap {
|
||||||
userName: String,
|
private map: UserAvailabilityHeatmapValue[];
|
||||||
availableTimes: AvailabilityTime[]
|
public maxNumberOfRespondents: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.map = [];
|
||||||
|
this.maxNumberOfRespondents = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
addName(unixTime: number, name: String): void {
|
||||||
|
if (this.map[unixTime] === undefined || this.map[unixTime] === null) {
|
||||||
|
this.map[unixTime] = { usersAvailableAtTime: [] } as UserAvailabilityHeatmapValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map[unixTime].usersAvailableAtTime.push(name);
|
||||||
|
|
||||||
|
if (this.map[unixTime].usersAvailableAtTime.length > this.maxNumberOfRespondents) {
|
||||||
|
this.maxNumberOfRespondents = this.map[unixTime].usersAvailableAtTime.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNamesAt(unixTime: number): String[] {
|
||||||
|
return this.map[unixTime]?.usersAvailableAtTime ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue