findtheti.me/frontend/src/components/AvailabilityPicker.tsx

199 lines
7.2 KiB
TypeScript
Raw Normal View History

2024-01-11 12:52:41 +02:00
import { Box, Stack } from "@mui/material";
import { MouseEvent, useState } from "react";
2024-01-10 00:18:42 +02:00
import "./css/AvailabilityPicker.css";
2024-01-11 12:52:41 +02:00
import { AvailabilityDay, OthersDay, OthersDays } from "../types/Availabilities";
import utils from "../utils";
import AvailabilityPickerDay from "./AvailabilityPickerDay";
import dayjs, { Dayjs } from "dayjs";
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import localizedFormat from 'dayjs/plugin/localizedFormat';
2024-01-09 17:23:40 +02:00
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(localizedFormat)
2024-01-11 12:52:41 +02:00
const HALFHOUR_DISPLAY_HEIGHT: number = 15;
2024-01-09 17:23:40 +02:00
2024-01-10 00:18:42 +02:00
type GhostPreviewProps = {
top: number,
left: number,
width: number,
height: number
2024-01-10 00:18:42 +02:00
}
const AvailabilityPicker = (props: {
2024-01-11 12:52:41 +02:00
days: AvailabilityDay[],
setDays: (days: AvailabilityDay[]) => void,
othersAvailabilities: OthersDays[],
2024-01-09 17:23:40 +02:00
eventType: String,
2024-01-11 12:52:41 +02:00
availabilityDurationInMinutes: number,
}) => {
2024-01-11 12:52:41 +02:00
2024-01-10 00:18:42 +02:00
const [ghostPreviewProps, setGhostPreviewProps] = useState<GhostPreviewProps | null>();
2024-01-09 17:23:40 +02:00
const displayGhostPeriod = (e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>, time: Dayjs) => {
2024-01-11 12:52:41 +02:00
let timeInMinutes = (time.hour() * 60.0) + time.minute();
let timeLeftInDayLessThanDuration = (timeInMinutes + props.availabilityDurationInMinutes) > 24.0 * 60.0;
2024-01-09 17:23:40 +02:00
2024-01-10 00:18:42 +02:00
if (timeLeftInDayLessThanDuration) {
2024-01-09 17:23:40 +02:00
return;
}
2024-01-10 00:18:42 +02:00
let scrollTop = document.getElementById('availability-picker')?.scrollTop ?? 0;
let scrollLeft = document.getElementById('availability-picker')?.scrollLeft ?? 0;
// @ts-ignore
2024-01-10 00:18:42 +02:00
const element = e.target.getBoundingClientRect();
2024-01-09 17:23:40 +02:00
2024-01-10 00:18:42 +02:00
setGhostPreviewProps({
// @ts-ignore
2024-01-10 00:18:42 +02:00
top: e.target?.offsetTop - scrollTop,
// @ts-ignore
2024-01-10 00:18:42 +02:00
left: e.target?.offsetLeft - scrollLeft,
width: element.width,
2024-01-11 12:52:41 +02:00
height: (props.availabilityDurationInMinutes/60.0) * 2 * HALFHOUR_DISPLAY_HEIGHT
2024-01-10 00:18:42 +02:00
})
2024-01-09 17:23:40 +02:00
}
2024-01-11 12:52:41 +02:00
const deleteAvailability = (day: AvailabilityDay, time: Dayjs) => {
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);
props.setDays([...props.days]);
}
}
const changeAvailability = (day: AvailabilityDay, time: Dayjs, isDelete: boolean) => {
if (isDelete) {
deleteAvailability(day, time);
return;
}
2024-01-10 00:18:42 +02:00
let fromTime = time;
let toTime = time.add(props.availabilityDurationInMinutes, "minutes");
2024-01-09 17:23:40 +02:00
2024-01-11 12:52:41 +02:00
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));
2024-01-09 17:23:40 +02:00
2024-01-10 00:18:42 +02:00
// the newly created availability crosses another single one. Both have the same from and to. Do nothing.
if (existingTimeContainingFrom >= 0 && existingTimeContainingTo >= 0 && existingTimeContainingFrom === existingTimeContainingTo) {
2024-01-09 17:23:40 +02:00
return;
}
2024-01-10 00:18:42 +02:00
// the newly created availability crosses 2 existing ones. Combine all of them into a single one.
if (existingTimeContainingFrom >= 0 && existingTimeContainingTo >= 0 && existingTimeContainingFrom !== existingTimeContainingTo) {
let newFrom = day.availableTimes[existingTimeContainingFrom].fromTime;
2024-01-11 12:52:41 +02:00
let newTo = day.availableTimes[existingTimeContainingTo].toTime;
2024-01-10 00:18:42 +02:00
2024-01-11 12:52:41 +02:00
day.availableTimes.splice(existingTimeContainingFrom, 1);
day.availableTimes.splice(existingTimeContainingTo, 1);
2024-01-09 17:23:40 +02:00
2024-01-10 00:18:42 +02:00
day.availableTimes.push({
fromTime: newFrom,
toTime: newTo
});
2024-01-09 17:23:40 +02:00
2024-01-11 12:52:41 +02:00
props.setDays([...props.days]);
2024-01-09 17:23:40 +02:00
return;
}
2024-01-10 00:18:42 +02:00
// The newly created availability from is within an existing one. Combine the 2 into one.
if (existingTimeContainingFrom >= 0 && existingTimeContainingTo < 0) {
let newFrom = day.availableTimes[existingTimeContainingFrom].fromTime;
2024-01-11 12:52:41 +02:00
day.availableTimes.splice(existingTimeContainingFrom, 1);
2024-01-10 00:18:42 +02:00
day.availableTimes.push({
fromTime: newFrom,
toTime: toTime
});
2024-01-09 17:23:40 +02:00
2024-01-11 12:52:41 +02:00
props.setDays([...props.days]);
2024-01-09 17:23:40 +02:00
return;
}
2024-01-10 00:18:42 +02:00
// The newly created availability to is within an existing one. Combine the 2 into one.
2024-01-11 12:52:41 +02:00
if (existingTimeContainingFrom < 0 && existingTimeContainingTo >= 0) {
let newTo = day.availableTimes[existingTimeContainingTo].toTime;
2024-01-09 17:23:40 +02:00
2024-01-11 12:52:41 +02:00
day.availableTimes.splice(existingTimeContainingTo, 1);
2024-01-09 17:23:40 +02:00
2024-01-10 00:18:42 +02:00
day.availableTimes.push({
fromTime: fromTime,
toTime: newTo
});
2024-01-09 17:23:40 +02:00
2024-01-11 12:52:41 +02:00
props.setDays([...props.days]);
2024-01-10 00:18:42 +02:00
return;
}
day.availableTimes.push({
fromTime: fromTime,
toTime: toTime
});
2024-01-09 17:23:40 +02:00
2024-01-11 12:52:41 +02:00
props.setDays([...props.days]);
2024-01-09 17:23:40 +02:00
}
return (
2024-01-10 00:18:42 +02:00
<Box
className={"availability-parent-box"}
2024-01-11 12:52:41 +02:00
onContextMenu={(e) => e.preventDefault()}
2024-01-09 17:23:40 +02:00
>
2024-01-10 00:18:42 +02:00
<Stack
id="availability-picker"
direction="row"
spacing={1}
justifyContent={"safe center"}
className={"availability-parent-stack"}
onScroll={(_) => setGhostPreviewProps(null)}
2024-01-10 00:18:42 +02:00
>
{
2024-01-11 12:52:41 +02:00
props.days.map(day =>
<AvailabilityPickerDay
key={day.forDate.unix()}
day={day}
eventType={props.eventType}
2024-01-11 12:52:41 +02:00
halfHourDisplayHeight={HALFHOUR_DISPLAY_HEIGHT}
othersAvailabilityDay={props.othersAvailabilities.map(a => {
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) => {
displayGhostPeriod(e, time);
}}
2024-01-11 12:52:41 +02:00
onMouseClickHalfhour={(day: AvailabilityDay, time: dayjs.Dayjs, isDelete: boolean) => {
changeAvailability(day, time, isDelete);
}}
/>
)
2024-01-10 00:18:42 +02:00
}
</Stack>
2024-01-09 17:23:40 +02:00
{
2024-01-10 00:18:42 +02:00
(ghostPreviewProps !== null) &&
<Box
className={"ghost-box"}
top={ghostPreviewProps?.top}
left={ghostPreviewProps?.left}
width={ghostPreviewProps?.width}
height={ghostPreviewProps?.height}
2024-01-10 00:18:42 +02:00
>
</Box>
2024-01-09 17:23:40 +02:00
}
2024-01-10 00:18:42 +02:00
</Box>
2024-01-09 17:23:40 +02:00
);
}
export default AvailabilityPicker;