mirror of
https://github.com/mvvasilev/findtheti.me.git
synced 2025-04-19 13:39:52 +03:00
Add disabled day display for large timezone differences. Disabled days will appear if a user is in a timezone so distant, that their availability pops into the next or previous day.
This commit is contained in:
parent
4abf196738
commit
7032156177
8 changed files with 124 additions and 62 deletions
|
@ -52,8 +52,8 @@ services:
|
|||
In the end, your folder structure should be as follows:
|
||||
```
|
||||
installationDir/
|
||||
| |-frontend/
|
||||
| |-dist/
|
||||
|-frontend/
|
||||
| |-dist/
|
||||
|-findtheti-me
|
||||
```
|
||||
5. Next, create a `.env` file in the root of the installation directory, and look at `.env.example` for what should be in there
|
||||
|
|
|
@ -60,8 +60,6 @@ const AvailabilityPicker = (props: {
|
|||
let existingTime = day.availableTimes.findIndex(t => utils.dayjsIsBetweenUnixExclusive(t.fromTime, time, t.toTime));
|
||||
|
||||
if (existingTime >= 0) {
|
||||
console.log(`delete ${existingTime} from`, day)
|
||||
|
||||
day.availableTimes.splice(existingTime, 1);
|
||||
|
||||
let dayIndex = props.days.findIndex(d => d.forDate.unix() === day.forDate.unix());
|
||||
|
@ -178,7 +176,6 @@ const AvailabilityPicker = (props: {
|
|||
key={day.forDate.unix()}
|
||||
day={day}
|
||||
eventType={props.eventType}
|
||||
currentTotalRespondents={props.availabilityHeatmap.maxNumberOfRespondents}
|
||||
halfHourDisplayHeight={HALFHOUR_DISPLAY_HEIGHT}
|
||||
availabilityHeatmap={props.availabilityHeatmap}
|
||||
onMouseEnterHalfhour={(e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { AvailabilityDay, UserAvailabilityHeatmap } from "../types/Availabilities";
|
||||
import utils from "../utils";
|
||||
import { Box, Card, Divider, Typography } from "@mui/material";
|
||||
import { Box, Card, Divider, Tooltip, Typography } from "@mui/material";
|
||||
import { EventTypes } from "../types/Event";
|
||||
import AvailabilityPickerHour from "./AvailabilityPickerHour";
|
||||
import "./css/AvailabilityPicker.css";
|
||||
|
@ -9,6 +9,7 @@ import utc from 'dayjs/plugin/utc';
|
|||
import timezone from 'dayjs/plugin/timezone';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import React, { useMemo } from "react";
|
||||
import { InfoOutlined } from "@mui/icons-material";
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
@ -18,7 +19,6 @@ const AvailabilityPickerDay = (props: {
|
|||
day: AvailabilityDay,
|
||||
eventType: String,
|
||||
halfHourDisplayHeight: number,
|
||||
currentTotalRespondents: number,
|
||||
availabilityHeatmap: UserAvailabilityHeatmap,
|
||||
onMouseEnterHalfhour: (e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs) => void,
|
||||
onMouseClickHalfhour: (day: AvailabilityDay, time: dayjs.Dayjs, isDelete: boolean) => void
|
||||
|
@ -28,23 +28,32 @@ const AvailabilityPickerDay = (props: {
|
|||
let hours: JSX.Element[] = [];
|
||||
|
||||
for (var i = 0; i < 24; i++) {
|
||||
let fullHourTime = props.day.forDate.set("hour", i);
|
||||
let halfHourTime = fullHourTime.add(30, "minutes");
|
||||
let fullHourTime = props.day.forDate.hour(i);
|
||||
let halfHourTime = utils.createHalfHourFromFullHour(fullHourTime);
|
||||
|
||||
hours.push(
|
||||
<AvailabilityPickerHour
|
||||
key={fullHourTime.unix()}
|
||||
disabled={props.day.disabled}
|
||||
dateTime={fullHourTime}
|
||||
halfHourDisplayHeight={props.halfHourDisplayHeight}
|
||||
currentTotalRespondents={props.currentTotalRespondents}
|
||||
currentTotalRespondents={props.availabilityHeatmap.maxNumberOfRespondents}
|
||||
namesMarkedFullHourAsAvailable={props.availabilityHeatmap.getNamesAt(fullHourTime.unix())}
|
||||
namesMarkedHalfHourAsAvailable={props.availabilityHeatmap.getNamesAt(fullHourTime.add(30, "minutes").unix())}
|
||||
namesMarkedHalfHourAsAvailable={props.availabilityHeatmap.getNamesAt(halfHourTime.unix())}
|
||||
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))}
|
||||
onMouseEnterHalfhour={(e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: dayjs.Dayjs): void => {
|
||||
if (props.day.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onMouseEnterHalfhour(e, time);
|
||||
}}
|
||||
onMouseClickOnHalfhour={(time: dayjs.Dayjs, isDelete: boolean): void => {
|
||||
if (props.day.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onMouseClickHalfhour(props.day, time, isDelete);
|
||||
}}
|
||||
/>
|
||||
|
@ -61,29 +70,33 @@ const AvailabilityPickerDay = (props: {
|
|||
className={"day-card"}
|
||||
variant="outlined"
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%" }}
|
||||
padding={1}
|
||||
>
|
||||
{
|
||||
(props.eventType === EventTypes.WEEK) &&
|
||||
<Box
|
||||
sx={{ width: "100%" }}
|
||||
padding={1}
|
||||
height={"50px"}
|
||||
>
|
||||
<Typography>
|
||||
{ props.day.forDate.format("dddd") }
|
||||
{
|
||||
(props.eventType === EventTypes.WEEK) && props.day.forDate.format("dddd")
|
||||
}
|
||||
{
|
||||
(props.eventType === EventTypes.DAY) && "Any Day"
|
||||
}
|
||||
{
|
||||
(props.eventType === EventTypes.DATE_RANGE || props.eventType === EventTypes.SPECIFIC_DATE) && props.day.forDate.format("LL")
|
||||
}
|
||||
{
|
||||
props.day.disabled &&
|
||||
<Tooltip
|
||||
title={props.day.disabled ? "This day is disabled and only shown as a result of timezone differences between yourself and another respondent" : ""}
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<InfoOutlined sx={{ ml: 1 }} fontSize="inherit" />
|
||||
</Tooltip>
|
||||
}
|
||||
</Typography>
|
||||
}
|
||||
{
|
||||
(props.eventType === EventTypes.DAY) &&
|
||||
<Typography>
|
||||
Any Day
|
||||
</Typography>
|
||||
}
|
||||
{
|
||||
(props.eventType === EventTypes.DATE_RANGE || props.eventType === EventTypes.SPECIFIC_DATE) &&
|
||||
<Typography>
|
||||
{ props.day.forDate.format("LL") }
|
||||
</Typography>
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider></Divider>
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ dayjs.extend(localizedFormat)
|
|||
|
||||
const AvailabilityPickerHour = (props: {
|
||||
dateTime: Dayjs,
|
||||
disabled: boolean,
|
||||
isFullHourSelected: boolean,
|
||||
isHalfHourSelected: boolean,
|
||||
halfHourDisplayHeight: number,
|
||||
|
@ -28,12 +29,16 @@ const AvailabilityPickerHour = (props: {
|
|||
}
|
||||
|
||||
const heatMapColorforValue = (value: number) => {
|
||||
if (value === 0 || props.currentTotalRespondents === 0) {
|
||||
return 'inherit';
|
||||
}
|
||||
// if (value === 0 || props.currentTotalRespondents === 0) {
|
||||
// return 'inherit';
|
||||
// }
|
||||
|
||||
// if (value === 1 && props.currentTotalRespondents === 1) {
|
||||
// return "hsl(" + 0 + ", 75%, 35%) !important";
|
||||
// }
|
||||
|
||||
var h = (1.0 - (value / props.currentTotalRespondents)) * 240
|
||||
return "hsl(" + h + ", 75%, 35%)";
|
||||
return "hsl(" + h + ", 75%, 35%) !important";
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -51,8 +56,10 @@ const AvailabilityPickerHour = (props: {
|
|||
enterDelay={500}
|
||||
>
|
||||
<Box
|
||||
className={classNames("full-hour", { "selected-availability": props.isFullHourSelected })}
|
||||
bgcolor={heatMapColorforValue(props.namesMarkedFullHourAsAvailable.length)}
|
||||
sx={{
|
||||
bgcolor: props.namesMarkedFullHourAsAvailable.length > 0 ? heatMapColorforValue(props.namesMarkedFullHourAsAvailable.length) : 'inherit'
|
||||
}}
|
||||
className={classNames("full-hour", { "selected-availability": props.isFullHourSelected, "hour-disabled": props.disabled })}
|
||||
height={props.halfHourDisplayHeight}
|
||||
onMouseEnter={(e) => props.onMouseEnterHalfhour(e, props.dateTime)}
|
||||
onMouseDown={(e) => {
|
||||
|
@ -78,16 +85,18 @@ const AvailabilityPickerHour = (props: {
|
|||
enterDelay={500}
|
||||
>
|
||||
<Box
|
||||
className={classNames("half-hour", { "selected-availability": props.isHalfHourSelected })}
|
||||
bgcolor={heatMapColorforValue(props.namesMarkedHalfHourAsAvailable.length)}
|
||||
sx={{
|
||||
bgcolor: props.namesMarkedHalfHourAsAvailable.length > 0 ? heatMapColorforValue(props.namesMarkedHalfHourAsAvailable.length) : 'inherit'
|
||||
}}
|
||||
className={classNames("half-hour", { "selected-availability": props.isHalfHourSelected, "hour-disabled": props.disabled })}
|
||||
height={props.halfHourDisplayHeight}
|
||||
onMouseEnter={(e) => props.onMouseEnterHalfhour(e, props.dateTime.add(30, "minutes"))}
|
||||
onMouseEnter={(e) => props.onMouseEnterHalfhour(e, utils.createHalfHourFromFullHour(props.dateTime))}
|
||||
onMouseDown={(e) => {
|
||||
if (e.button !== 0 && e.button !== 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onMouseClickOnHalfhour(props.dateTime.add(30, "minutes"), e.button === 2);
|
||||
props.onMouseClickOnHalfhour(utils.createHalfHourFromFullHour(props.dateTime), e.button === 2);
|
||||
}}
|
||||
/>
|
||||
</DisableableTooltip>
|
||||
|
|
|
@ -22,7 +22,7 @@ div.hour-light {
|
|||
background-color: var(--hour-light-color);
|
||||
}
|
||||
|
||||
div.hour-dark {
|
||||
div.hour-disabled {
|
||||
width: 100%;
|
||||
border-top: solid 1px;
|
||||
border-color: var(--hour-border-bolor);
|
||||
|
@ -47,7 +47,7 @@ div.selected-availability {
|
|||
background-color: var(--hover-color);
|
||||
} */
|
||||
|
||||
div.full-hour:active, div.half-hour:active {
|
||||
div.full-hour:not(.hour-disabled):active, div.half-hour:not(.hour-disabled):active {
|
||||
background-color: var(--active-color);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,8 +33,6 @@ export default function ExistingEventPage() {
|
|||
useEffect(() => {
|
||||
utils.showSpinner();
|
||||
|
||||
let localTimezone = dayjs.tz.guess();
|
||||
|
||||
Promise.all([
|
||||
utils.performRequest(`/api/events/${eventId}`)
|
||||
.then(result => setEvent({
|
||||
|
@ -52,6 +50,8 @@ export default function ExistingEventPage() {
|
|||
.then((result: [{ id: number, from_date: string, to_date: string, user_name: string }]) => {
|
||||
let heatmap = new UserAvailabilityHeatmap();
|
||||
|
||||
let localTimezone = dayjs.tz.guess();
|
||||
|
||||
const LENGTH_OF_30_MINUTES_IN_SECONDS = 1800;
|
||||
|
||||
for (const availability of result) {
|
||||
|
@ -61,7 +61,30 @@ export default function ExistingEventPage() {
|
|||
let startUnix = start.unix();
|
||||
let endUnix = end.unix();
|
||||
|
||||
for (var timeInUnix = startUnix; timeInUnix <= endUnix; timeInUnix += LENGTH_OF_30_MINUTES_IN_SECONDS) {
|
||||
let startDay = start.startOf("day");
|
||||
let endDay = end.startOf("day");
|
||||
|
||||
// Add day on which this availability period is starts on ( timezone difference could be so large that it's in the previous day )
|
||||
// This day will be disabled and unavailable for adding own availability, unless it falls within the events own from-to range.
|
||||
if (!heatmap.daysWhenAvailabilitiesPresent.some(d => d.forDate.unix() === startDay.unix())) {
|
||||
heatmap.daysWhenAvailabilitiesPresent.push({
|
||||
forDate: startDay,
|
||||
disabled: true,
|
||||
availableTimes: []
|
||||
});
|
||||
}
|
||||
|
||||
// Add day on which this availability period is set to end ( timezone difference could be so large that it flips to the next day )
|
||||
// This day will be disabled and unavailable for adding own availability, unless it falls within the events own from-to range.
|
||||
if (end.unix() !== endDay.unix() && !heatmap.daysWhenAvailabilitiesPresent.some(d => d.forDate.unix() === endDay.unix())) {
|
||||
heatmap.daysWhenAvailabilitiesPresent.push({
|
||||
forDate: endDay,
|
||||
disabled: true,
|
||||
availableTimes: []
|
||||
});
|
||||
}
|
||||
|
||||
for (var timeInUnix = startUnix; timeInUnix < endUnix; timeInUnix += LENGTH_OF_30_MINUTES_IN_SECONDS) {
|
||||
heatmap.addName(timeInUnix, availability.user_name);
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +93,7 @@ export default function ExistingEventPage() {
|
|||
})
|
||||
.catch(e => toast.error(e))
|
||||
])
|
||||
.finally(() => utils.hideSpinner());;
|
||||
.finally(() => utils.hideSpinner())
|
||||
|
||||
}, [eventId]);
|
||||
|
||||
|
@ -106,25 +129,41 @@ export default function ExistingEventPage() {
|
|||
let localFromDate = event.fromDate.tz(localTimezone);
|
||||
let localToDate = event.toDate.tz(localTimezone);
|
||||
|
||||
var days: AvailabilityDay[] = [];
|
||||
|
||||
switch (event.eventType) {
|
||||
case EventTypes.SPECIFIC_DATE: {
|
||||
createAvailabilitiesBasedOnInitialDate(localFromDate, 1);
|
||||
days = createAvailabilitiesBasedOnInitialDate(localFromDate, 1);
|
||||
break;
|
||||
}
|
||||
case EventTypes.DATE_RANGE: {
|
||||
createAvailabilitiesBasedOnInitialDate(localFromDate, Math.abs(localFromDate.diff(localToDate, "day", false)));
|
||||
days = createAvailabilitiesBasedOnInitialDate(localFromDate, Math.abs(localFromDate.diff(localToDate, "day", false)));
|
||||
break;
|
||||
}
|
||||
case EventTypes.DAY: {
|
||||
createAvailabilitiesBasedOnUnspecifiedInitialDate(1, localTimezone);
|
||||
days = createAvailabilitiesBasedOnUnspecifiedInitialDate(1, localTimezone);
|
||||
break;
|
||||
}
|
||||
case EventTypes.WEEK: {
|
||||
createAvailabilitiesBasedOnUnspecifiedInitialDate(7, localTimezone);
|
||||
days = createAvailabilitiesBasedOnUnspecifiedInitialDate(7, localTimezone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [event]);
|
||||
|
||||
availabilityHeatmap?.daysWhenAvailabilitiesPresent.forEach(hd => {
|
||||
let createdDay = days.find(cd => cd.forDate.unix() === hd.forDate.unix());
|
||||
|
||||
if (createdDay) {
|
||||
createdDay.disabled = false;
|
||||
} else {
|
||||
days.push(hd);
|
||||
}
|
||||
});
|
||||
|
||||
days.sort((a, b) => a.forDate.unix() - b.forDate.unix());
|
||||
|
||||
setDays([...days])
|
||||
}, [event, availabilityHeatmap]);
|
||||
|
||||
useEffect(() => {
|
||||
var valid = !utils.isNullOrUndefined(userName) && userName !== "";
|
||||
|
@ -134,23 +173,24 @@ export default function ExistingEventPage() {
|
|||
setCanSubmit(valid);
|
||||
}, [userName, days]);
|
||||
|
||||
const createAvailabilitiesBasedOnUnspecifiedInitialDate = (numberOfDays: number, tz: string) => {
|
||||
createAvailabilitiesBasedOnInitialDate(dayjs.tz("1970-01-05 00:00:00", tz), numberOfDays);
|
||||
const createAvailabilitiesBasedOnUnspecifiedInitialDate = (numberOfDays: number, tz: string): AvailabilityDay[] => {
|
||||
return createAvailabilitiesBasedOnInitialDate(dayjs.tz("1970-01-05 00:00:00", tz), numberOfDays);
|
||||
}
|
||||
|
||||
const createAvailabilitiesBasedOnInitialDate = (date: Dayjs, numberOfDays: number) => {
|
||||
const createAvailabilitiesBasedOnInitialDate = (date: Dayjs, numberOfDays: number): AvailabilityDay[] => {
|
||||
let availabilities: AvailabilityDay[] = [];
|
||||
|
||||
for (var i: number = 0; i < numberOfDays; i++) {
|
||||
let availability: AvailabilityDay = {
|
||||
forDate: date.add(i, "day").startOf("day"),
|
||||
disabled: false,
|
||||
availableTimes: []
|
||||
}
|
||||
|
||||
availabilities.push(availability);
|
||||
}
|
||||
|
||||
setDays(availabilities);
|
||||
return availabilities;
|
||||
}
|
||||
|
||||
const submitAvailabilities = () => {
|
||||
|
|
|
@ -7,6 +7,7 @@ export type AvailabilityTime = {
|
|||
|
||||
export type AvailabilityDay = {
|
||||
forDate: Dayjs,
|
||||
disabled: boolean,
|
||||
availableTimes: AvailabilityTime[]
|
||||
}
|
||||
|
||||
|
@ -15,12 +16,11 @@ export type UserAvailabilityHeatmapValue = {
|
|||
}
|
||||
|
||||
export class UserAvailabilityHeatmap {
|
||||
private map: UserAvailabilityHeatmapValue[];
|
||||
public maxNumberOfRespondents: number;
|
||||
private map: any = {};
|
||||
public maxNumberOfRespondents: number = 0;
|
||||
public daysWhenAvailabilitiesPresent: AvailabilityDay[] = [];
|
||||
|
||||
constructor() {
|
||||
this.map = [];
|
||||
this.maxNumberOfRespondents = 0;
|
||||
}
|
||||
|
||||
addName(unixTime: number, name: String): void {
|
||||
|
|
|
@ -78,6 +78,9 @@ const utils = {
|
|||
},
|
||||
isNullOrUndefined: (thing: any): boolean => {
|
||||
return thing === null || thing === undefined;
|
||||
},
|
||||
createHalfHourFromFullHour: (fullHour: Dayjs): Dayjs => {
|
||||
return fullHour.add(30, "minutes");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue