2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スケジュール管理アプリにタイマー機能を実装した話

2
Posted at

はじめに

スケジュールを立てる能力を育てるアプリを開発しました。
タイマーの実装したのだが、それの実装に四苦八苦したので、備忘録として書いておきます。

使用ファイル

ログインに使っているファイルは以下です。

実装本体

  • src/components/pages/Dashboard.tsx
    タイマー画面の中心。スタート、ストップ、経過時間表示、time_entries への保存を担当。
  • src/supabase/supabaseFunction/supabaseFunction.tsx
    addTimeEntry, fetchTimeEntries, updateTimeEntry を定義。タイマー実績のDB操作。
  • src/components/pages/FullCalendarComponent.tsx
    保存済みの time_entries をカレンダー上に実績イベントとして表示。クリックで編集ダイアログを開く。
  • src/components/pages/TimeEntryForm.tsx
    保存済み実績 time_entries の編集フォーム。
  • database.types.ts
    time_entries テーブル、time_entry_status 型の定義。

間接的に関係するファイル

  • src/hooks/AuthContext.tsx
    タイマー保存時に必要なログインユーザー情報を useAuth で提供。
  • src/router/Router.tsx
    Dashboard などタイマー関連画面へのルーティング。
  • src/router/ProtectedRoute.tsx
    タイマー画面を未ログインユーザーから保護。

解説

以下重要な部分をピックアップしました。

src/components/pages/Dashboard.tsx

タイマー画面のコンポーネントです。スタート、ストップ、経過時間表示、time_entries への保存を担当をしています。

Date型を日本語の形式に変換する関数

export const formatDateTime = (date: Date | null) => {
  if (!date) return '-';
  return date.toLocaleString('ja-JP');
};

Date型を日本語の形式に変換する関数です。引数にはdateでその中身がDateオブジェクトまたはnullになります。
そして、dateの中身がない時( if (!date) return '-';)は、-を返します。これは、日時が未設定のときに、表示するためです。
中身がある場合(return date.toLocaleString('ja-JP');)は、toLocaleString('ja-JP') で日本語ロケールの日時文字列に変換して返しています。
こうすると、環境にもよりますが例えばこんな表示になります。

2026/4/17 10:30:00

ミリ秒をhh:mm:ss形式の文字列に変換する関数

Date.now()を使って現在の時間を取得するにあたり、戻り値がミリ秒になります。そのため「時間」「分」「秒」に変換を行う関数を用意しました。
return [hours, minutes, seconds].map((value) => String(value).padStart(2, '0')).join(':');
ここで、経過時間を、画面に表示しやすい「時:分:秒」 形式に変換しています。

isRunning が true の間だけ、1秒ごとに now を更新する処理

スタートボタンが押された時にisRunningがtrueになり、useEffectが動きます。
useEffect(() => {}, [isRunning]);

setInterval は、指定した間隔ごとに処理を繰り返す関数です。1000msつまり1秒単位でsetNow(Date.now());の処理を繰り返します。

const timer = window.setInterval(() => {
  setNow(Date.now());
}, 1000);

setNow(Date.now());は現在の時刻をnowに登録します。
そうすると、Reactは再レンダリングされます。

再レンダリングされると以下のコードが動き、開始から何秒たったのかを計算します。

const elapsedMs = useMemo(() => {
  if (!startedAt) return 0;

  const end = isRunning ? now : stoppedAt?.getTime() ?? startedAt.getTime();

  return Math.max(0, end - startedAt.getTime());
}, [isRunning, now, startedAt, stoppedAt]);

こちらは画面表に使われます。

おわりに

前回ログイン機能も作成したのですが、こちらも配慮が必要なものになってきています。

参考

Git hubプロジェクト
アプリ画面

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?