0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

timer画面の本実装 - テーブル描画と非同期通信の試行錯誤 - Day 12

0
Last updated at Posted at 2026-05-13

はじめに

Day 11では、swagger-typescript-apiを用いたAPIクライアント自動生成の導入について書きました。

今回はtimer画面の本実装について書きます。テーブル描画の実装過程でハマったポイントと、非同期通信の問題を記録します。


画面の構成

今回のツールは機能ごとにメインエリアを切り替える仕様です。timer画面は以下のエリアで構成されています。

エリア 役割
タイマーエリア カウントアップ表示
ボタンエリア start・stopボタン
ボタンログエリア startボタン押下時のバックエンド戻り値表示
CSV表示エリア 当日分の記録をテーブル形式で表示
統計エリア NN機能による統計データ表示(未実装)

タイマーエリア

タイマーはフロントエンド側でカウントアップを描画しています。startボタン押下時にsetIntervalで1秒ごとにカウントを進め、stopボタン押下時にclearIntervalでカウントを停止します。

実際の時間計測はバックエンドのstart_checkerend_checkerが行っています。フロントのカウンターは表示専用であり、計測結果はバックエンドがCSVに保存します。再始動時はカウントを0に戻してから再開する設計にしています。

this.start_timer.addEventListener("click", () => {
    if(isTracking === true){
        alert('It has already started.')
        return
    }
    clearInterval(this.timer_click)
    count = 0

    this._click_button()
    isTracking = !isTracking
    this.timer_click = setInterval(() => {
    count++;
    const h = Math.floor(count / 3600)
    const m = Math.floor((count % 3600) / 60)
    const s = count % 60
    this.timer.textContent = String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0')}, 1000)
})

// stop
this.end_timer.addEventListener("click", () => {
    if(isTracking === false){
        alert('Not started')
        return
    }
    api.timer.timerEndTimerStopPost()
    isTracking = !isTracking
    clearInterval(this.timer_click)
    this._csv_log()
})

ボタンログエリア

ボタンログエリアには、startボタン押下時のバックエンド戻り値を表示しています。

CSV表示エリアはstopを押下してはじめて更新されるため、startが正しく押下されているかを即座に確認できません。この問題を解消するため、startボタン押下時にstart_checkerの戻り値をそのまま表示する領域を設けました。バックエンドへの保存は行わず、表示専用の役割です。

private async _click_button(){
    const start_data = (await api.timer.timerStartTimerStartGet()).data
    this.button_log.append(start_data + '\n')
}

CSV表示エリアの実装

CSV表示エリアには当日分の記録をテーブル形式で表示しています。当日分のフィルタリングはバックエンドのimport_to_csvが行っており(Day 10参照)、フロントは受け取ったデータをそのまま描画します。

表示するのはstart-time・end-time・totalの3カラムのみです。当日分のみを表示する仕様のため日付カラムは除外しており、インデックス指定(row[1]row[3]row[4])でカラムを取り出しています。

private async _csv_log(){
    const csv_data = (await api.timer.todayTimerTimerTodayGet()).data
    this.csv_body.innerHTML = ''
    csv_data.forEach((row: String) => {
        const tr = document.createElement('tr')
        const data1 = row[1]
        const data3 = row[3]
        const data4 = row[4]
        const td1 = document.createElement('td')
        td1.classList.add('td1')
        const td3 = document.createElement('td')
        td3.classList.add('td3')
        const td4 = document.createElement('td')
        td4.classList.add('td4')
        td1.textContent = data1 +' / '
        td3.textContent = data3 +' / '
        td4.textContent = data4
        tr.appendChild(td1)
        tr.appendChild(td3)
        tr.appendChild(td4)
        this.csv_body.appendChild(tr)
    });
}

this.csv_body.innerHTML = ''でtbodyを初期化してから描画しています。初期化を行わないとstopのたびに行が追記され重複が発生するため、この処理が必要でした。


ハマったポイント

forEachの戻り値

実装当初、APIレスポンスを処理する際にforEachの戻り値を変数に代入しようとしていましたが、forEachundefinedを返すため意図した動作になりませんでした。配列の各要素に対してDOM操作を行う場合は戻り値を使わず、ループ内で直接操作する方針に切り替えました。


統計エリア

統計エリアは現時点では領域の確保のみを行っています。NN機能によるタスク進捗の統計グラフを表示する予定ですが、NN機能の実装完了後に対応します。


おわりに

今日はtimer画面の本実装について書きました。

テーブル描画はシンプルな処理に見えて、tbody初期化・非同期処理の順序・forEachの扱いと詰まる箇所が重なりました。awaitの問題は引き続き修正予定です。

Day 13ではgoals画面の仮実装について書く予定です。timer画面との構造の違いと、レイアウト設計の判断を記録します。


この記事は連載「クラウドに依存しないマイルストーン管理ツール開発記」のDay 12です。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?