如何在 FullCalendar 中在新创建的事件旁边显示弹出窗口?

问题描述 投票:0回答:0

我正在 React 项目中使用 FullCalendar,并尝试在选择时间范围时在新创建的事件旁边显示弹出窗口(使用浮动 ui)。 (选择时间范围会创建一个标题为空的事件,弹出窗口最终应该编辑该事件的详细信息。现在我只是想让弹出窗口正确显示。)

选择时间范围后,如何在 FullCalendar 中新创建的事件旁边显示弹出框?有人能指出我正确的方向吗?

我正在使用弹窗,因为它们在对另一个问题的回复中被建议。

我已经成功地创建了事件并分别显示了弹出窗口,但是我无法将两者结合起来以便将弹出窗口定位在事件旁边。

我尝试使用 setTimeout 等待事件元素在 DOM 中可用,然后获取元素的边界矩形来定位弹出框。但是,这似乎不起作用,我仍然无法正确定位弹出窗口。

我尝试修改 handleSelect 函数以获取所选事件的位置并将其存储在组件状态中,然后更新 Popover 组件以接受该位置作为道具并将该位置应用于其样式。在 Calendar 组件中,我将存储的位置传递给 Popover 组件。这也没有用——当我创建一个新事件时什么也没有发生。

在下面的代码中,我定义了一个 Popover 组件,它在单击按钮时显示一个弹出框。使用 FullCalendar 的日历组件显示事件。因此,当用户在日历上选择一个时间范围时,将创建一个新的临时事件......最终目标是弹出窗口出现在它旁边(以提供一个用于将更新应用于事件规范的界面)。

import React, { useState, useRef } from 'react';
import FullCalendar from '@fullcalendar/react';
import timeGridPlugin from '@fullcalendar/timegrid';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import customDayHeaderContent from './customDayHeaderContent';
import { nanoid } from 'nanoid';
import {
  useFloating,
  autoUpdate,
  offset,
  flip,
  shift,
  useDismiss,
  useRole,
  useClick,
  useInteractions,
  FloatingFocusManager,
  useId
} from "@floating-ui/react";

function Popover() {
  const [isOpen, setIsOpen] = useState(false);

  const { x, y, refs, strategy, context } = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
    middleware: [
      offset(10),
      flip({ fallbackAxisSideDirection: "end" }),
      shift()
    ],
    whileElementsMounted: autoUpdate
  });

  const click = useClick(context);
  const dismiss = useDismiss(context);
  const role = useRole(context);

  const { getReferenceProps, getFloatingProps } = useInteractions([
    click,
    dismiss,
    role
  ]);

  const headingId = useId();

  return (
    <>
      <div style={{ position: 'static' }}>
        <button ref={refs.setReference} {...getReferenceProps()}>
          Add review
        </button>
        {isOpen && (
          <FloatingFocusManager context={context} modal={false}>
            <div
              className="Popover z-10"
              ref={refs.setFloating}
              style={{
                position: 'absolute', // Change this to absolute
                top: y ?? 0,
                left: x ?? 0
              }}
              aria-labelledby={headingId}
              {...getFloatingProps()}
            >
              <h2 id={headingId}>Review balloon</h2>
              <textarea placeholder="Write your review..." />
              <br />
              <button
                style={{ float: "right" }}
                onClick={() => {
                  console.log("Added review.");
                  setIsOpen(false);
                }}
              >
                Add
              </button>
            </div>
          </FloatingFocusManager>
        )}
      </div>
    </>
  );
}

export default function Calendar() {
  const [events, setEvents] = useState([
    { title: "YM's birthday", date: '2023-03-21' },
    { title: 'Anniversary', date: '2023-03-22' },
    {
      title: 'Review',
      start: '2023-03-21T00:00:00',
      end: '2023-03-21T03:00:00',
      extendedProps: { department: 'BioChemistry' },
      description: 'Lecture',
    },
    {
      title: 'Class',
      start: '2023-03-22T13:00:00',
      end: '2023-03-22T15:00:00',
      extendedProps: { department: 'BioChemistry' },
      description: 'Lecture',
    },
  ]);

  const calendarRef = useRef(null);
  const [eventTitle, setEventTitle] = useState('');
  const [selectedEvent, setSelectedEvent] = useState(null);

  const handleSelect = (selectInfo) => {
    const calendarApi = selectInfo.view.calendar;
    calendarApi.unselect(); // clear date selection
  
    const newEventId = `temp-event-${nanoid()}`; // Generate a new unique ID for the temporary event
  
    const newEvent = {
      id: newEventId,
      title: '',
      start: selectInfo.startStr,
      end: selectInfo.endStr,
      allDay: selectInfo.allDay,
    };
  
    setSelectedEvent(newEvent);
    calendarApi.addEvent(newEvent); // Add a temporary event with an empty title
    setIsOpen(true);
  
    // Cleanup function to remove the temporary event when component unmounts or selection is changed
    return () => {
      const tempEvent = calendarApi.getEventById(newEventId);
      if (tempEvent) {
        tempEvent.remove();
      }
    };
  };
  
  const handleSaveEvent = () => {
    const calendarApi = calendarRef.current.getApi();
  
    if (selectedEvent) {
      const tempEvent = calendarApi.getEventById(selectedEvent.id);
      if (tempEvent && selectedEvent.title !== '') {
        tempEvent.setProp('title', selectedEvent.title); // Update the title of the temporary event
        tempEvent.setExtendedProp('id', nanoid()); // Set a unique ID for the saved event
      } else {
        tempEvent.remove(); // Remove the temporary event if no title is provided
      }
    }
    setIsOpen(false);
  };
  
  const [isOpen, setIsOpen] = useState(false);

  const updateEventTitle = (title) => {
    if (selectedEvent) {
      const updatedEvent = { ...selectedEvent, title };
      setSelectedEvent(updatedEvent);
      const calendarApi = calendarRef.current.getApi();
      const tempEvent = calendarApi.getEventById(selectedEvent.id);
      if (tempEvent) {
        tempEvent.setProp('title', updatedEvent.title); // Update the title of the temporary event in real-time
      }
    }
  };

  return (
    <>
      <FullCalendar
        ref={calendarRef}
        plugins={[timeGridPlugin, dayGridPlugin, interactionPlugin]}
        initialView="timeGridWeek"
        height="100%"
        nowIndicator={true}
        editable={true}
        selectable={true}
        eventResizableFromStart={true}
        dayHeaderContent={customDayHeaderContent}
        select={handleSelect}
        titleFormat={{
          year: 'numeric',
          month: 'long'
        }}
        dayHeaderFormat={{
          weekday: 'short',
          month: 'numeric',
          day: 'numeric',
          omitCommas: true
        }}
        headerToolbar={{
          left: 'title',
          // center: 'title',
          right: 'prev,today,next timeGridDay,timeGridWeek,dayGridMonth'
        }}
        views={{
          timeGridWeek: {
            type: 'timeGrid',
            duration: { days: 7 },
            buttonText: 'week',
            dateAlignment: 'week',
            dayHeaderFormat: { day: 'numeric', weekday: 'short', omitCommas: true },
            eventTimeFormat: {
              hour: 'numeric',
              minute: '2-digit',
              omitZeroMinute: true,
              meridiem: 'short'
            },
          },
          dayGridMonth: {
            type: 'dayGrid',
            duration: { months: 1 },
            buttonText: 'month',
            dayHeaderFormat: { weekday: 'short' },
            eventTimeFormat: {
              hour: 'numeric',
              minute: '2-digit',
              omitZeroMinute: true,
              meridiem: 'short'
            },
          },
        }}
        eventMinHeight={50}
        events={events}
      />
      <Popover />
    </>
  );
}

现在是这样(单独工作): 这就是梦想(一起工作):

reactjs fullcalendar popover fullcalendar-6 floating-ui
© www.soinside.com 2019 - 2024. All rights reserved.