zero-one-uiプロジェクトで作ったUIです。 Reactを使って、フルスクラッチでカレンダーを実装しています。
月間、週間、日間、指定した期間のカレンダーなどを実装しています。 それぞれのカレンダーで、ドラッグ&ドロップを使用してイベントを作成したり、イベントの期間を変更したり、イベントを別の日時に移動することができます。 画面左の小さいカレンダーから、カレンダーに表示する期間を変更することもできます。
ブラウザ上で動作するカレンダーの実装方法に興味があったため、実際に作ってみました。
様々な形式のカレンダーを表示することができるのですが、どれもカレンダー行とカレンダー列の2つで構成されおり、 それぞれの要素の中に、行イベントや列イベントなどが表示されます。 カレンダー行とカレンダー列に分けることで、柔軟にカレンダーを構築することが可能になっています。
週間、日間、指定した期間のカレンダーは、カレンダー列を日数分並べたものになります。 また、カレンダー列を並べて表示されるカレンダーでは、複数の日にまたがるイベントは、 カレンダー上部の1行だけ表示されているカレンダー行に表示されます。
カレンダーを作っていると、連続した日付や連続した時刻が必要になったり、特定の日付が指定した期間に含まれているかを判断したくなることが結構あります。 日付を操作するためのライブラリであるdate-fnsには、そういった操作のための関数が豊富に含まれていたので、ほとんど自分で実装する必要がなく便利でした。 例えば、以下のようなコードで今日の連続した時刻を取得することができます。
tsconst date = new Date(); eachHourOfInterval({ start: startOfDay(date), end: endOfDay(date) });
カレンダーのイベントは、作成、期間の変更、移動などを行うことができます。 それぞれの操作を一つのhookにまとめることで、特定の操作のロジックの変更を、一つのファイルで行えるように工夫しています。
カレンダーのイベントは削除することができるのですが、一定時間内であれば取り消せるような実装を行っています。 はじめはtanstack-queryのキャッシュの中からイベントを削除するようにしていたのですが、refetchされると画面に表示されてしまうため、 グローバルな状態として削除しようとしているイベントのidを持たせて、 イベントのリストを取得するhookの内部で、削除しようとしているイベントを除外するような実装を行っています。
関連するコードをまとめることで変更容易性を高めることができるというのは理解していましたが、 このプロジェクトを通して、それをより実感しました。 イベントは複数の操作が可能なのですが、初期の段階では、一つの操作のロジックが2つのコンポーネントに散らばっていたり、 各操作のロジックが一つのコンポーネントに混在していました。 そのため、区別するために変数名が冗長になったり、変更が必要な際に適切な修正箇所を特定するのが難しくなるなどの問題が発生していました。 それぞれの操作をhookにまとめることで、そういった問題を解決することができました。