search envelope-o feed check
Home Unanswered Active Tags New Question
user comment-o

On windows resize, Cell Loses selection. [Calendar]

Asked by Anonymous
25 days ago.

As title says. Select a cell then resize the window, i lose my selection.

import React, {
  FunctionComponent,
  memo,
  useEffect,
  useRef,
  useState,
} from "react";
import { DayPilotCalendar, DayPilot } from "daypilot-pro-react";
import { useAgendaSetup } from "../hooks/useAgendaSetup";
import { customAppointment, onEventPicked } from "../utils/AgendaUtils";
import useWindowDimensions from "../../../hooks/useWindowDimensions";
import {
  findEmployeeById,
  getFormattedHours,
  onEventMoving,
  setHourToCell,
} from "../../../utils/AgendaUtils";
import { TableSettings } from "../../../interfaces/settings/table_settings";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store";
import { OverlayScrollbars } from "overlayscrollbars";
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import {
  afterCellAreas,
  onBeforeColumnHeaderRender,
  onHeaderClick,
  renderCellAreas,
} from "../utils/DaypilotCalendarUtils";
import { useQueryClient } from "@tanstack/react-query";
import { ensureColumnVisibility } from "../utils/SidebarUtils";
import {
  styled,
  Drawer as MuiDrawer,
  Backdrop,
  capitalize,
} from "@mui/material";
import ReactDOMServer, { renderToStaticMarkup } from "react-dom/server";

import "./AgendaGrid.scss";
import AgendaSidebar, { SidebarState } from "../AgendaSidebar/AgendaSidebar";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { AgendaGridProvider } from "../context/AgendaGridContext";
import { clsx } from "clsx";
import ExCalendar from "../../../components/ExCalendar/ExCalendar";
import { Drawer, Dropdown, MenuProps, Space } from "antd";
import useDeviceOrientation from "../../../hooks/useDeviceOrientation";
import { useAppointmentsCache } from "../AgendaSidebar/section/AgendaSidebarTabSection/tabs/hooks/useAppointmentsCache";
import ExDropdown from "../../../components/ExDropdown/ExDropdown";
import { useDialogManager } from "../../../hooks/useDialogManager";
import ClockRightIcon from "../../assets/icons/clock-rotate-right.svg?react";
import useAgendaStore from "../AgendaSidebar/store/useAgendaStore";
import moment from "moment";
import { useGenericDrawer } from "../../../drawers/useGenericDrawer/useGenericDrawer";
import Loader from "../../../components/Loader/Loader";
import NiceModal from "@ebay/nice-modal-react";
import ModalConfirmation from "../../../modals/ConfirmationModal/ModalConfirmation";
import { usePersonalPermissionCreationOrUpdate } from "../../../api/data-hooks/usePersonalPermissionCreationOrEdit";
import { AuthProvider } from "../../../interfaces/auth/AuthProvider";

interface AgendaGridProps {}

const AgendaGrid: FunctionComponent<
  AgendaGridProps
> = ({}: AgendaGridProps) => {
  dayjs.extend(isBetween);
  const settings: TableSettings = useSelector(
    (state: RootState) => state.tableSettings.settings as TableSettings
  );

  const calendarRef = useRef<DayPilotCalendar>(null);
  const { handlePersonalPermissionMutation } =
    usePersonalPermissionCreationOrUpdate();
  const { height, width } = useWindowDimensions();
  const { orientation } = useDeviceOrientation();
  const { isOpen, closeDrawer, openDrawer } = useGenericDrawer();
  const authProvider: AuthProvider = useSelector(
    (state: RootState) => state.auth.auth
  );
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
  const [isSidebarHidden, setIsSidebarHidden] = useState(false);

  const [isTableResized, setIsTableResized] = useState(false);
  const [selectColumn, setSelectColumn] = useState(0);
  const [selectedEvents, setSelectedEvents] = useState<
    DayPilot.Event[] | undefined
  >([]);
  const [targetCell, setTargetCell] = useState<any>();
  const [startHour, setStartHour] = useState<string>("");
  const [resourceId, setResourceId] = useState<string>("");

  const {
    colHeaders,
    appointments,
    dbAppointments,
    onEventDnd,
    onEventResize,
    startDayHour,
    endDayHour,
    agendaDayData,
    cellDuration,
    startWorkHour: posStartHour,
    endWorkHour: posEndHour,
    formattedDate: date,
    isLoading: isCalendarLoading,
    isRefetching,
  } = useAgendaSetup({
    fetchAppointments: true,
    calendarRef: calendarRef,
  });

  const actualDate = useRef(date);

  useEffect(() => {
    if (!isSidebarOpen) {
      deactivateMovingMode();
      if (actualDate.current !== date) {
        actualDate.current = date;
      }
    }
  }, [date, isSidebarOpen]);

  useEffect(() => {
    if (!isMovingEventMode) onCloseDrawer();
  }, [date]);

  const { syncAppointmentCache } = useAppointmentsCache({
    services: [],
  });

  const calendarContainer = document?.querySelector(
    "div.calendar_default_scroll >div > div:nth-child(2)"
  );

  const container = document.getElementById("calendar_container");

  function isValidFormat(date: any, format: any) {
    const regex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})$/;
    if (!regex.test(date)) {
      return false;
    }
    const parsedDate = dayjs(date);
    return parsedDate.format(format) === date;
  }

  const dateToCheck = "2024-07-25T11:09:04.186";
  const format = "YYYY-MM-DDTHH:mm:ss.SSS";

  const isValid = isValidFormat(dateToCheck, format);

  console.log(isValid);

  function onCloseDrawer() {
    if (!isMovingEventMode) {
      setIsSidebarOpen(false);
      setIsTableResized(false);
      deactivateMovingMode();
      setTimeout(() => {
        syncAppointmentCache();
      }, 1500);
      setTimeout(() => {
        calendarRef.current?.control.multiselect.clear();
      }, 200);
    }
  }

  const { showChangelogDialog } = useDialogManager();

  useEffect(() => {
    if (settings.tableConfig?.popup != null) {
      showChangelogDialog(settings.tableConfig?.popup);
    }
  }, []);

  if (calendarContainer && /iPad|iPhone|iPod/.test(navigator.platform)) {
    const container = document.querySelector("#day-pilot-calendar-container");
    if (container) {
      OverlayScrollbars(container as any, {
        overflow: {
          y: "hidden",
        },
      });
    }
  }

  const queryClient = useQueryClient();
  const { isMovingEventMode, deactivateMovingMode } = useAgendaStore();

  useEffect(() => {
    if (isMovingEventMode && width < 1024) {
      setIsSidebarHidden(true);
      setIsTableResized(false);
    } else if (isMovingEventMode && width > 1024) {
      setIsSidebarHidden(false);
    }
  }, [isMovingEventMode, width]);

  useEffect(() => {
    if (!isMovingEventMode) {
      if (isSidebarOpen === false) {
        setIsTableResized(false);
        setTimeout(() => {
          setSelectedEvents([]);
        }, 500);
      } else {
        setTimeout(() => {
          setIsTableResized(true);
        }, 100);
        setTimeout(() => {
          if (calendarContainer) {
            ensureColumnVisibility(selectColumn, width, calendarContainer!);
          }
        }, 300);
      }
    }
  }, [isSidebarOpen]);

  const items: MenuProps["items"] =
    settings.tableConfig?.mod_agenda?.employees.map((employee) => ({
      key: employee.id,
      label: capitalize(employee.first_name ?? ""),
    }));

  const config: any = {
    showCurrentTimeMode: "Full",
    viewType: "Resources",
    locale: "it-IT",
    useEventBoxes: "Never",
    startDate: date,
    scrollDelayCells: 0,
    headerHeightAutoFit: false,
    events: appointments,
    columns: colHeaders,
    showCurrentTime: true,
    allowMultiRange: false,
    allowMultiSelect: true,
    snapToGrid: true,
    snapToGridTimeRangeSelecting: true,
    snapToGridEventMoving: false,
    snapToGridEventResizing: false,
    rectangleSelectHandling: "Disabled",
    headerHeight: 50,
    rowHeaderWidth: 100,
    cellHeight: cellDuration * (43 / 15),
    timeHeaderCellDuration: cellDuration,
    cellDuration: cellDuration,
    scrollDelayDynamic: 0,
    cellSweeping: false,
    EventHoverHandling: "Bubble",
    columnWidth:
      (width - 62) / colHeaders.length >= 160
        ? (width - 62) / colHeaders.length
        : 160,

    crosshairType: "Disabled",
    columnWidthSpec: "Fixed",
    columnMarginRight: 10,
    columnMarginLeft: 10,

    dynamicEventRendering: "Disabled",
    eventArrangement: "SideBySide",
    eventClickHandling: "JavaScript",
    dayBeginsHour: startDayHour,
    businessBeginsHour: posStartHour,
    businessEndsHour: posEndHour,
    dayEndsHour: endDayHour,

    onHeaderClick: (args: any) => {
      console.log(
        settings.tableConfig!.mod_agenda.days[
          moment(date).format("YYYY-MM-DD")
        ]?.["employees"]?.[args.header.id]?.["work_time"]
      );

      onHeaderClick(
        args,
        queryClient,
        date,
        settings.tableConfig?.current_pos.id_pos! ?? "",
        settings.tableConfig!.mod_agenda.days[
          moment(date).format("YYYY-MM-DD")
        ]?.["employees"]?.[args.header.id]?.["work_time"],
        () =>
          openDrawer("absenceDrawer", {
            id_employee: args.header.id,
          })
      );
    },
    onBeforeHeaderRender: (args: any) => {
      if (agendaDayData) {
        const isDefaultHour =
          agendaDayData?.employees[args.header.id].work_time[0].default;
        onBeforeColumnHeaderRender(args, isDefaultHour);
      }
    },
    onBeforeEventDomAdd: (args: any) => {
      const isSelected =
        calendarRef.current?.control.multiselect.events().some((event) => {
          return (
            event.data.id === args.e.data.id ||
            event.data.tag.client_id === args.e.data.tag.client_id
          );
        }) || args.e.data.tag["type"] === "draft";

      args.element = customAppointment(
        args,
        isSelected ?? false,
        args.e.data.tag.group
      );
    },
    onBeforeCellRender: (args: any) => {
      const colIndex = colHeaders.findIndex(
        (column: any) => column.id === args.resource
      );
      renderCellAreas(agendaDayData, args);
    },
    onAfterCellRender: (args: any) => {
      let date =
        args.cell.start.getHours() +
        ":" +
        (args.cell.start.getMinutes() === 0
          ? "00"
          : args.cell.start.getMinutes());

      args.div?.firstElementChild.setAttribute(
        "data-after",
        isMovingEventMode ? "Sposta qui: " + date : true ? date : ""
      );

      const colIndex = colHeaders.findIndex(
        (column: any) => column.id === args.cell.resource
      );

      afterCellAreas(agendaDayData, args, colIndex, date);
    },
    onBeforeEventRender: (args: any) => {
      if (args.data.tag.group === "personal_appointment") {
        args.data.borderColor = "rgba(255, 126, 152, 1)";
        args.data.backColor = "rgba(255, 251, 251, 1)";
        args.data.fontColor = "rgba(255, 126, 152, 1)";
      }

      args.data.areas = [
        {
          width: "15px",
          height: "15px",
          right: 10,
          bottom: 10,
          icon: "fas fa-trash",
        },

        {
          width: "400px",
          height: 10,
          right: 0,
          top: 0,
          action: "ResizeStart",
          cssClass: "resize_area",
        },

        {
          width: "400px",
          height: "30px",
          right: 0,
          bottom: 0,
          action: "ResizeEnd",
          cssClass: "resize_area",
        },
      ];
    },
    onTimeRangeSelecting: (args: any) => {},
    onTimeRangeSelected: (args: any) => {
      /*  setTargetCell(args);
      if (!isMovingEventMode) {
        setSelectedEvents([]);

        const columnIndex = colHeaders.findIndex(
          (resource) => resource.id === args.resource
        );
        syncAppointmentCache();

        setTimeout(() => {
          if (isSidebarOpen === false) {
            setIsSidebarOpen(!isSidebarOpen);
          } else {
            ensureColumnVisibility(columnIndex, width, calendarContainer!);
          }
        }, 600);
        setTimeout(() => {
          const hour = dayjs(args.start).format("HH:mm");
          setStartHour(hour);
          setResourceId(args.resource);

          calendarRef.current?.control.multiselect.clear();

          setSelectColumn(columnIndex);
        }, 300);
        (calendarRef.current?.control as any)?.selectTimeRange(
          args.start,
          args.start,
          args.resource,
          true
        );
      } */
    },
    onEventResized: (args: any) => {
      const {
        newStart: start,
        newEnd: end,
        e: {
          data: { id: eventId },
        },
      } = args;

      const { formattedStart, formattedEnd, minutesDifference } =
        getFormattedHours(start, end, cellDuration);

      if (isNaN(Number(eventId))) {
        onEventResize.mutate({
          eventId: eventId,
          formattedStart: formattedStart,
          formattedEnd: formattedEnd,
          type: args.e.data.tag.group,
          duration:
            minutesDifference < cellDuration ? cellDuration : minutesDifference,
        });
      }
    },
    onEventMoving: (args: any) => {
      onEventMoving(args);
    },
    onEventMove: (args: any) => {},
    onEventMoved: (args: any) => {
      const eventId = args.e.data.id;
      const { newStart: start, newEnd: end, newResource: resource } = args;

      if (true) {
        const { formattedStart, formattedEnd, minutesDifference } =
          getFormattedHours(start, end, cellDuration);
        const formattedResource = findEmployeeById(
          settings.tableConfig?.mod_agenda?.employees || [],
          resource
        );

        if (isNaN(Number(eventId))) {
          onEventDnd.mutate({
            eventId: eventId,
            formattedStart: formattedStart,
            end: formattedEnd,
            id_staff: resource,
            type: args.e.data.tag.group,
          });
        }
      }
    },
    onEventClicked: (args: any) => {
      console.log(args);
      if (args.e.data.tag.group === "appointment") {
        const isSelected = calendarRef.current?.control.multiselect
          .events()
          .some((event) => {
            return event.data.tag.client_id === args.e.data.tag.client_id;
          });

        setTimeout(() => {
          if (isSelected === true) {
            setTimeout(() => {
              setIsSidebarOpen(false);
              calendarRef.current?.control.multiselect.clear();
            }, 600);
          } else {
            calendarRef.current?.control.multiselect.clear();

            const days = calendarRef.current?.control.events.list.filter(
              (element) => element.tag.client_id === args.e.data.tag.client_id
            );

            days?.forEach((day) => {
              const dayValue = calendarRef.current?.control.events.find(
                day.id.toString()
              );
              if (dayValue) {
                calendarRef.current?.control.multiselect.add(dayValue);
              }
            });
            setTimeout(() => {
              const events = calendarRef.current?.control.multiselect.events();

              const columnIndex = colHeaders.findIndex(
                (resource) => resource.id === args.e.data.resource
              );
              setSelectColumn(columnIndex);
              setSelectedEvents(events);

              if (events && events.length > 0) {
                const sortedEvents = events.sort(
                  (a: DayPilot.Event, b: DayPilot.Event) =>
                    dayjs(a.start() as any).diff(dayjs(b.start() as any))
                );

                const startHour = dayjs(sortedEvents[0].start() as any).format(
                  "HH:mm"
                );
                setStartHour(startHour);
              }
              setIsSidebarOpen(true);
            }, 0);
          }
        }, 500);
      } else {
        openDrawer("absenceDrawer", {
          id_employee: args.e.data.resource,
          body: args.e.data.tag.permission_body,
        });
        /*    NiceModal.show(ModalConfirmation, {
          title: "Conferma eliminazione dei permessi",
          subtitle: "Vuoi eliminare questo periodi di pausa selezionato?",
          onConfirm: () => {
            const appointment = {
              id_booking_diary:
                settings.tableConfig?.mod_agenda.options.id_diary,
              id_appointment: args.e.data.id,
              state: "deleted",
            };

            const dataRequest = {
              email: authProvider.email,
              token: authProvider.token,
              pin: authProvider.pin,
              appointment_date: date,
              personal_appointments: [appointment],
            };
            handlePersonalPermissionMutation.mutate(dataRequest);
          },
          considerNavbar: false,
        }); */
      }
      /*   const isSelected = calendarRef.current?.control.multiselect
        .events()
        .some(
          (event) => event.data.tag.client_id === args.e.data.tag.client_id
        );
      if (isSelected) {
        calendarRef.current.?control.multiselect.clear();
        setTimeout(() => {
          setIsSidebarOpen(false);
        }, 1000);
      } else {
        onEventPicked(calendarRef, args.e.data.tag.client_id, dbAppointments);
        setTimeout(() => {
          const events = calendarRef.current?.control.multiselect.events();
          const columnIndex = colHeaders.findIndex(
            (resource) => resource.id === args.e.data.resource
          );
          setSelectColumn(columnIndex);
          setSelectedEvents(events);
          setIsSidebarOpen(true);
        }, 750);
      } */
    },
  };

  return (
    <>
      <div
        className="animate-fade"
        id="calendar_container"
        style={{
          height: `${(height ?? 150) - 50}px`,
          width: `calc(100% - ${isTableResized ? "400" : "0"}px)`,
          willChange: "auto",
        }}
      >
        <DayPilotCalendar
          ref={calendarRef}
          heightSpec={"Parent100Pct"}
          {...config}
        />
      </div>
      <div
        className={clsx("", {
          "opacity-0": isSidebarHidden,
        })}
        style={{
          visibility: isSidebarHidden ? "hidden" : "visible",
        }}
      >
        <AgendaSidebar
          calendarRef={calendarRef}
          isSidebarOpen={isSidebarOpen}
          startHour={startHour}
          resource={resourceId}
          startingSidebarState={SidebarState.create}
          selectedServices={selectedEvents || []}
          targetCell={targetCell}
          userId={
            selectedEvents && selectedEvents?.length > 0
              ? selectedEvents[0].data.tag.client_id
              : undefined
          }
          date={actualDate.current?.valueOf() ?? ""}
          handleSidebarHiding={() => {
            setIsSidebarHidden(false);
            setIsTableResized(true);
          }}
          closeSidebar={() => {
            onCloseDrawer();
          }}
        />
      </div>
    </>
  );
};

export default AgendaGrid;
Answer posted by Dan Letecky [DayPilot]
24 days ago.

Your config contains dynamic values that depend on the window width (columnWidth). Changing the config invokes a Calendar update.

At this moment, the Calendar is designed to clear the current time range selection during update.

It might be possible to add an option to change this behavior, but we need to check it.

New Reply
This reply is
Attachments:
or drop files here
Your name (optional):