【Next.js】Fullcalendarで外部イベントをドラッグ＆ドロップする

Fullcalendar+reactで外部のイベントをドラッグ＆ドロップしている記事が見つけられなかったので書いてみました。

### 構成
- react+typescript
- MUI
- Fullcalendar

### reactにFullcalendarの導入

ここら辺は飛ばしますので下記を参考にしてください。

https://fullcalendar.io/docs/react

https://github.com/fullcalendar/fullcalendar-examples/tree/main/react-mobx-typescript

### 基本のカレンダー

![2023-02-28 10.12のイメージ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3122920/aaa2804b-14ff-4d65-2c50-a5375536d0bc.jpeg)


```ts:index.ts
import React from 'react';
// FullCalendar
import FullCalendar from '@fullcalendar/react';
import timeGridPlugin from '@fullcalendar/timegrid';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import jaLocale from '@fullcalendar/core/locales/ja';
import listPlugin from '@fullcalendar/list';
import { Grid } from '@mui/material';

const SampleCalendar: React.FC = () => {
  const ref = React.createRef<any>();

  return (
    <Grid container>
      <FullCalendar
        ref={ref}
        locales={[jaLocale]}
        plugins={[timeGridPlugin, dayGridPlugin, interactionPlugin, listPlugin]}
        initialView='dayGridMonth'
        slotDuration='00:30:00'
        headerToolbar={{
          left: 'prev,next today',
          center: 'title',
          right: 'dayGridMonth,timeGridWeek',
        }}
        //
        selectable={true}
        weekends={true}
      />
    </Grid>
  );
};

export default SampleCalendar;
```

### 外部イベントdrop可能にする

droppableをtrueにすることで、内部外部に関わらずイベントのdropが可能になります。
外部イベントをdropした時に発火するdropに指定します。
eventDropと間違えないように注意してください。
こちらは内部のイベントをdropした時に発火します。

```diff_tsx:index.ts
const drop=()=>{
console.log('dropした')
}

 <FullCalendar
// 省略
+ droppable={true}
+ drop={drop}
/>

```

### 外部イベントコンポーネント作成

dropができるだけのシンプルな外部イベントを作成します。

```diff_tsx:components/ExternalEvents.tsx
import React, { useEffect, useRef } from "react";
import { Draggable } from "@fullcalendar/interaction";
import { Box } from "@mui/material";

type Props = {
  event: { id: number; title: string };
};

export const ExternalEvent = ({ event }: Props) => {
  const elRef = useRef<HTMLButtonElement | null>(null);

  useEffect(() => {
    if (!elRef.current) return;

    const draggable = new Draggable(elRef.current, {
      eventData: () => {
        return { ...event, create: true };
      },
    });

    return () => draggable.destroy();
  });

  return (
    <Box
      component="div"
      ref={elRef}
      title={event.title}
      sx={{
        fontWeight: "bold",
        cursor: "grab",
        mb: "0.6rem",
        mx: "1.5rem",
      }}
    >
      {event.title}
    </Box>
  );
};

```


### 外部イベントを反映

これで完成です。

```diff_tsx:index.ts
import React from 'react';
// FullCalendar
import React from "react";
// FullCalendar
import FullCalendar from "@fullcalendar/react";
import timeGridPlugin from "@fullcalendar/timegrid";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import jaLocale from "@fullcalendar/core/locales/ja";
import listPlugin from "@fullcalendar/list";
import { Grid } from "@mui/material";
import { ExternalEvent } from "@/conponents/externalEvents";

const events = [
  {
    id: "1",
    title: "脚トレ",
  },
  {
    id: "2",
    title: "胸トレ",
  },
  {
    id: "3",
    title: "背中トレ",
  },
];

const SampleCalendar: React.FC = () => {
  const ref = React.createRef<any>();

  const drop = () => {
    console.log("dropした");
  };

  return (
    <Grid container>
      <Grid item xs={3}>
        {events.map((event) => (
          <ExternalEvent event={event} key={event.id} />
        ))}
      </Grid>
      <Grid item xs={9}>
        <FullCalendar
          ref={ref}
          locales={[jaLocale]}
          plugins={[
            timeGridPlugin,
            dayGridPlugin,
            interactionPlugin,
            listPlugin,
          ]}
          initialView="dayGridMonth"
          slotDuration="00:30:00"
          headerToolbar={{
            left: "prev,next today",
            center: "title",
            right: "dayGridMonth,timeGridWeek",
          }}
          //
          selectable={true}
          weekends={true}
          droppable={true}
          drop={drop}
        />
      </Grid>
    </Grid>
  );
};

export default SampleCalendar;
```