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,
|
2024-01-10 10:19:54 +02:00
|
|
|
width: number,
|
|
|
|
height: number
|
2024-01-10 00:18:42 +02:00
|
|
|
}
|
|
|
|
|
2024-01-10 10:19:54 +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-10 10:19:54 +02:00
|
|
|
}) => {
|
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
|
|
|
|
2024-01-10 10:19:54 +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;
|
|
|
|
|
2024-01-10 10:19:54 +02:00
|
|
|
// @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({
|
2024-01-10 10:19:54 +02:00
|
|
|
// @ts-ignore
|
2024-01-10 00:18:42 +02:00
|
|
|
top: e.target?.offsetTop - scrollTop,
|
2024-01-10 10:19:54 +02:00
|
|
|
// @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
|
2024-01-10 10:19:54 +02:00
|
|
|
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"}
|
2024-01-10 10:19:54 +02:00
|
|
|
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
|
2024-01-10 10:19:54 +02:00
|
|
|
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;
|
|
|
|
})}
|
2024-01-10 10:19:54 +02:00
|
|
|
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 10:19:54 +02:00
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)
|
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
|
2024-01-10 10:19:54 +02:00
|
|
|
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
|
|
|
);
|
2024-01-10 10:19:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export default AvailabilityPicker;
|