イベントの扱い

DayFlow におけるイベントは、ビューやプラグインすべての土台になるデータ構造です。ここではイベント定義の考え方、作成・更新・削除の方法、そしてバックエンド連携までをまとめて紹介します。

イベントインターフェース

日付/時刻はすべて Temporal API をベースにしています。必要に応じて以下 3 種類を使い分けます。

  • PlainDate:終日イベント(開始・終了日のみ欲しい場合)
  • PlainDateTime:タイムゾーンを意識しないローカルの予定 ➜ 標準はこちらを推奨
  • ZonedDateTime:タイムゾーン込みの予定(海外会議・移動など)
import { Temporal } from 'temporal-polyfill';
import { Event } from '@dayflow/core';
プロパティ説明必須
idstringイベントを一意に識別する ID必須
titlestringカレンダーに表示するタイトル必須
startTemporal.PlainDate | Temporal.PlainDateTime | Temporal.ZonedDateTime開始日時。イベント種別に合わせて型を選択必須
endTemporal.PlainDate | Temporal.PlainDateTime | Temporal.ZonedDateTime終了日時。start と同じ型を使う必須
descriptionstringメモや詳細。表示方法は自由任意
allDaybooleantrue なら終日イベントとして描画任意
iconboolean | Nodeイベント用カスタムアイコン。true(デフォルト):デフォルト表示、false:非表示、Node:カスタムアイコン任意
calendarIdstring単一カレンダー帰属のカテゴリ ID任意
calendarIdsstring[]複数カレンダーに同時帰属させる ID のリスト。設定時は calendarId より優先。リスト内のいずれかが表示中なら予定も表示される。複数色の斜線パターン背景で描画される。任意
metaRecord<string, any>任意の追加データ(場所・参加者など)任意

イベントの作り方

ヘルパー関数(最も簡単)

createEvent などのヘルパーは Date を渡すだけで内部で Temporal に変換してくれます。

import {
  createEvent,
  createAllDayEvent,
  createTimedEvent,
} from '@dayflow/core';
import '@dayflow/core/dist/styles.css';

const meeting = createEvent({
  id: '1',
  title: 'チームミーティング',
  start: new Date(2024, 9, 15, 10, 0),
  end: new Date(2024, 9, 15, 11, 0),
});

const holiday = createAllDayEvent('2', 'カンファレンス', new Date(2024, 9, 20));

const lunch = createTimedEvent(
  '3',
  'ランチ休憩',
  new Date(2024, 9, 15, 12, 0),
  new Date(2024, 9, 15, 13, 0)
);

Temporal を直接扱う

日時の粒度を厳密に制御したい場合はインターフェースを直接実装します。

import { Temporal } from 'temporal-polyfill';
import { Event } from '@dayflow/core';

const localEvent: Event = {
  id: '1',
  title: 'ローカル会議',
  start: Temporal.PlainDateTime.from({
    year: 2024,
    month: 10,
    day: 15,
    hour: 10,
  }),
  end: Temporal.PlainDateTime.from({
    year: 2024,
    month: 10,
    day: 15,
    hour: 11,
  }),
};

const allDayEvent: Event = {
  id: '2',
  title: 'カンファレンス',
  start: Temporal.PlainDate.from('2024-10-20'),
  end: Temporal.PlainDate.from('2024-10-22'),
  allDay: true,
};

const timezoneEvent: Event = {
  id: '3',
  title: '国際ミーティング',
  start: Temporal.ZonedDateTime.from('2024-10-16T14:00:00[America/New_York]'),
  end: Temporal.ZonedDateTime.from('2024-10-16T15:00:00[America/New_York]'),
};

メタデータ付きのイベント

meta は UI に露出しない任意の情報を詰め込めます。

import { createEvent } from '@dayflow/core';

const event = createEvent({
  id: '3',
  title: '顧客打ち合わせ',
  description: 'Q4 のロードマップ確認',
  start: new Date(2024, 9, 16, 14, 0),
  end: new Date(2024, 9, 16, 15, 0),
  meta: {
    location: 'Zoom',
    attendees: ['john@example.com', 'jane@example.com'],
    recurring: false,
  },
});

イベントの管理

DayFlow では useCalendarApp から返る calendar インスタンスに対して CRUD を行います。

追加

calendar.addEvent(event);

const calendar = useCalendarApp({
  views: [createMonthView()],
  events: [event1, event2],
});

更新

calendar.updateEvent('event-id', {
  title: 'タイトルを更新',
  start: new Date(2024, 9, 15, 11, 0),
  end: new Date(2024, 9, 15, 12, 0),
});

// 3 つ目の引数を true にするとドラッグ中などの一時状態として扱える
calendar.updateEvent('event-id', updatedEvent, true);

削除・取得

calendar.deleteEvent('event-id');

const allEvents = calendar.getEvents();
const { events } = calendar; // 現在の状態をそのまま参照

コールバックと状態管理

使用しているフレームワーク(React, Vue, Svelte, Angular)の状態管理と組み合わせると次のようになります。

import { useState } from 'react';
import {
  useCalendarApp,
  DayFlowCalendar,
  createMonthView,
  Event,
} from '@dayflow/core';

function MyCalendar() {
  const [events, setEvents] = useState<Event[]>([]);

  const calendar = useCalendarApp({
    views: [createMonthView()],
    events,
    callbacks: {
      onEventCreate: event => setEvents(prev => [...prev, event]),
      onEventUpdate: event =>
        setEvents(prev => prev.map(e => (e.id === event.id ? event : e))),
      onEventDelete: eventId =>
        setEvents(prev => prev.filter(e => e.id !== eventId)),
      onEventDoubleClick: (event, e) => {
        console.log('イベントがダブルクリックされました:', event);
        // e.currentTarget をカスタムポップオーバーのアンカーとして使用
      },
    },
  });

  return <DayFlowCalendar calendar={calendar} />;
}

バックエンドと同期する場合は非同期処理をコールバックに直接書いて問題ありません。

const calendar = useCalendarApp({
  views: [createMonthView()],
  events,
  callbacks: {
    onEventCreate: async event => {
      const saved = await api.createEvent(event);
      setEvents(prev => [...prev, saved]);
    },
    onEventUpdate: async event => {
      await api.updateEvent(event);
      setEvents(prev => prev.map(e => (e.id === event.id ? event : e)));
    },
    onEventDelete: async eventId => {
      await api.deleteEvent(eventId);
      setEvents(prev => prev.filter(e => e.id !== eventId));
    },
  },
});

カレンダータイプと装飾

calendarIdcalendars 設定を組み合わせると、予定ごとに色やスタイルを切り替えられます。

import { createEvent, useCalendarApp, createMonthView } from '@dayflow/core';

const workEvent = createEvent({
  id: '1',
  title: 'デザインレビュー',
  start: new Date(2024, 9, 15, 14, 0),
  end: new Date(2024, 9, 15, 15, 0),
  calendarId: 'work',
});

const personalEvent = createEvent({
  id: '2',
  title: '歯医者',
  start: new Date(2024, 9, 16, 10, 0),
  end: new Date(2024, 9, 16, 11, 0),
  calendarId: 'personal',
});

const calendars = [
  {
    id: 'work',
    name: '仕事',
    colors: {
      eventColor: '#3b82f6',
      eventSelectedColor: '#2563eb',
      lineColor: '#3b82f6',
      textColor: '#ffffff',
    },
    isVisible: true,
  },
  {
    id: 'personal',
    name: 'プライベート',
    colors: {
      eventColor: '#10b981',
      eventSelectedColor: '#059669',
      lineColor: '#10b981',
      textColor: '#ffffff',
    },
    isVisible: true,
  },
];

const calendar = useCalendarApp({
  views: [createMonthView()],
  events: [workEvent, personalEvent],
  calendars,
});

複数カレンダーへの同時帰属

動作仕様

  • calendarIds が設定されている場合、calendarId より優先されます。
  • 表示ルール:リスト内のカレンダーがひとつでも表示中であれば予定も表示されます。
  • ビジュアル:複数カレンダーの予定は 45° 斜線パターン背景(カレンダーごとの色が交互に入ります)と、左端の多色グラデーションバーで一目でわかるようになります。
  • 選択中:プライマリカレンダー(calendarIds[0])のソリッドカラーに戻ります。

使用例

import { createEvent, useCalendarApp, createWeekView } from '@dayflow/core';

// "team" と "marketing" の両カレンダーに帰属させる
const sharedEvent = createEvent({
  id: 'shared-1',
  title: '全社ミーティング',
  start: new Date(2024, 9, 15, 10, 0),
  end: new Date(2024, 9, 15, 11, 30),
  calendarIds: ['team', 'marketing'], // 複数カレンダー
});

// 3 カレンダーにまたがる終日予定
const crossCalendarDay = {
  id: 'shared-2',
  title: 'チーム合宿',
  start: Temporal.PlainDate.from('2024-10-20'),
  end: Temporal.PlainDate.from('2024-10-22'),
  allDay: true,
  calendarIds: ['team', 'personal', 'travel'],
};

const calendars = [
  {
    id: 'team',
    name: 'チーム',
    colors: {
      eventColor: '#3b82f6',
      lineColor: '#3b82f6',
      eventSelectedColor: '#2563eb',
      textColor: '#fff',
    },
    isVisible: true,
  },
  {
    id: 'marketing',
    name: 'マーケティング',
    colors: {
      eventColor: '#f59e0b',
      lineColor: '#f59e0b',
      eventSelectedColor: '#d97706',
      textColor: '#fff',
    },
    isVisible: true,
  },
  {
    id: 'personal',
    name: 'プライベート',
    colors: {
      eventColor: '#10b981',
      lineColor: '#10b981',
      eventSelectedColor: '#059669',
      textColor: '#fff',
    },
    isVisible: true,
  },
  {
    id: 'travel',
    name: '旅行',
    colors: {
      eventColor: '#8b5cf6',
      lineColor: '#8b5cf6',
      eventSelectedColor: '#7c3aed',
      textColor: '#fff',
    },
    isVisible: true,
  },
];

const calendar = useCalendarApp({
  views: [createWeekView()],
  events: [sharedEvent, crossCalendarDay],
  calendars,
});

複数日にまたがる予定

import { Temporal } from 'temporal-polyfill';
import { Event } from '@dayflow/core';

const conference: Event = {
  id: '1',
  title: 'Tech Conference 2024',
  start: Temporal.PlainDate.from('2024-10-20'),
  end: Temporal.PlainDate.from('2024-10-22'),
  allDay: true,
};

const nightShift: Event = {
  id: '2',
  title: '夜勤シフト',
  start: Temporal.ZonedDateTime.from('2024-10-15T22:00:00[America/New_York]'),
  end: Temporal.ZonedDateTime.from('2024-10-16T06:00:00[America/New_York]'),
};

イベントメタデータ

meta フィールドにはあらゆる追加情報を格納できます。

import { Temporal } from 'temporal-polyfill';
import { Event } from '@dayflow/core';

const event: Event = {
  id: '1',
  title: 'プロジェクトキックオフ',
  start: Temporal.ZonedDateTime.from('2024-10-15T10:00:00[America/New_York]'),
  end: Temporal.ZonedDateTime.from('2024-10-15T11:00:00[America/New_York]'),
  meta: {
    // ミーティング詳細
    location: '会議室 A',
    meetingUrl: 'https://zoom.us/j/123456',

    // 参加者情報
    organizer: 'john@example.com',
    attendees: ['jane@example.com', 'bob@example.com'],

    // カスタムフィールド
    project: 'プロジェクト X',
    priority: 'high',
    tags: ['planning', 'kickoff'],

    // 繰り返し情報
    recurring: true,
    recurrenceRule: 'FREQ=WEEKLY;BYDAY=MO',

    // その他任意のデータ
    customField: 'custom value',
  },
};

ベストプラクティス

  1. ID は必ず一意に:UUID もしくはバックエンドの ID をそのまま使う
  2. ヘルパーを優先createEvent 系で 9 割のユースケースを賄える
  3. 型の選択を意識:終日 = PlainDate、通常 = PlainDateTime、時差対応 = ZonedDateTime
  4. calendarId / calendarIds で分類:単一カレンダーは calendarId、複数カレンダー帰属は calendarIds を使う
  5. meta を活用:場所や出席者など UI に依存しない情報をここへ
  6. 開始と終了の整合性をチェックstart < end を守るとバグを防げる
  7. バッチ更新を検討:大量更新はまとめて updateEvents 相当の処理に寄せる

型リファレンス

日付と時刻の処理の詳細については以下を参照してください。

  • イベントインターフェース:src/types/event.ts

関連ドキュメント

On this page