イベントの扱い
DayFlow におけるイベントは、ビューやプラグインすべての土台になるデータ構造です。ここではイベント定義の考え方、作成・更新・削除の方法、そしてバックエンド連携までをまとめて紹介します。
イベントインターフェース
日付/時刻はすべて Temporal API をベースにしています。必要に応じて以下 3 種類を使い分けます。
- PlainDate:終日イベント(開始・終了日のみ欲しい場合)
- PlainDateTime:タイムゾーンを意識しないローカルの予定 ➜ 標準はこちらを推奨
- ZonedDateTime:タイムゾーン込みの予定(海外会議・移動など)
import { Temporal } from 'temporal-polyfill';
import { Event } from '@dayflow/core';| プロパティ | 型 | 説明 | 必須 |
|---|---|---|---|
id | string | イベントを一意に識別する ID | 必須 |
title | string | カレンダーに表示するタイトル | 必須 |
start | Temporal.PlainDate | Temporal.PlainDateTime | Temporal.ZonedDateTime | 開始日時。イベント種別に合わせて型を選択 | 必須 |
end | Temporal.PlainDate | Temporal.PlainDateTime | Temporal.ZonedDateTime | 終了日時。start と同じ型を使う | 必須 |
description | string | メモや詳細。表示方法は自由 | 任意 |
allDay | boolean | true なら終日イベントとして描画 | 任意 |
icon | boolean | Node | イベント用カスタムアイコン。true(デフォルト):デフォルト表示、false:非表示、Node:カスタムアイコン | 任意 |
calendarId | string | 単一カレンダー帰属のカテゴリ ID | 任意 |
calendarIds | string[] | 複数カレンダーに同時帰属させる ID のリスト。設定時は calendarId より優先。リスト内のいずれかが表示中なら予定も表示される。複数色の斜線パターン背景で描画される。 | 任意 |
meta | Record<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));
},
},
});カレンダータイプと装飾
calendarId と calendars 設定を組み合わせると、予定ごとに色やスタイルを切り替えられます。
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',
},
};ベストプラクティス
- ID は必ず一意に:UUID もしくはバックエンドの ID をそのまま使う
- ヘルパーを優先:
createEvent系で 9 割のユースケースを賄える - 型の選択を意識:終日 = PlainDate、通常 = PlainDateTime、時差対応 = ZonedDateTime
calendarId/calendarIdsで分類:単一カレンダーはcalendarId、複数カレンダー帰属はcalendarIdsを使うmetaを活用:場所や出席者など UI に依存しない情報をここへ- 開始と終了の整合性をチェック:
start < endを守るとバグを防げる - バッチ更新を検討:大量更新はまとめて
updateEvents相当の処理に寄せる
型リファレンス
日付と時刻の処理の詳細については以下を参照してください。
- イベントインターフェース:
src/types/event.ts