概要
個人開発で、Redmine・Backlog等からのスムーズな移行に対応したチーム向けタスク管理 SaaS Taski(タスキ) を開発・運用しています。
タスク管理ツールの定番機能といえば**ガントチャート(工程表)**ですが、Redmine、Backlog、Asana、Jootoなど、世の中の主要なプロジェクト管理ツールのガントチャートを触っていて、ずっと抱えていた強い違和感がありました。
「なぜどのツールも、頑なに『日単位』までしか対応してくれないのか?」
現実の開発現場や業務では、以下のような「半日単位の締め切り」が日常茶飯事です。
- 「リリース作業は 9月15日の正午(12:00)まで に完了させる」
- 「午前中に成果物をレビュー提出し、午後から修正対応に入る」
- 「午前休を取るメンバーがいるので、作業着手は午後から」
しかし、既存のツールはすべて最小単位が「1日」です。その結果、現場ではどうなるかというと……
「チケットのタイトルに『【12:00まで】本番デプロイ』と手書きする」
「ガントチャートは大枠だけにして、時間単位の管理はGoogleカレンダーで二重管理する」
という、涙ぐましい泥臭い妥協運用がまかり通っています。
「それなら、最初から**午前・午後(半日スケール)**に対応したガントチャートを作ればいいのでは?」と思い立ち、自前で設計・実装しました。本記事では、なぜ主要ツールは時間単位をやらないのかという技術的罠と、それを解決する「半日スケール」の設計・React実装テクニックを公開します。
1. なぜ主要SaaSは「時間単位ガントチャート」を作らないのか?
「日単位が不満なら、1時間単位(または30分単位)のガントチャートを作ればいいじゃないか」と最初は思いました。
しかし、実際にプロトタイプを作ってみて**主要ツールがこれを避ける理由(罠)**が骨身に染みてわかりました。
罠①: 画面が「横スクロール地獄」になり、鳥瞰性が死ぬ
ガントチャートの最大の価値は、**「プロジェクト全体の進捗やボトルネックを一目で俯瞰できる(鳥瞰性)」**にあります。
もし1時間刻み(1日あたり稼働8時間)で表示するとどうなるでしょう。
1スロットの幅を文字が読める最小限の 40px としても:
- 1日: 8スロット × 40px = 320px
- 2週間のスプリント(10営業日): 10日 × 320px = 3,200px
- 1ヶ月: 20日 × 320px = 6,400px
たった2〜3週間のタスクを見通すだけで、画面幅(一般的なディスプレイで 1,200px〜1,920px)を大幅に超過し、猛烈な横スクロールを強いられます。「全体を見渡す」というガントチャート本来の価値が完全に崩壊するのです。
罠②: DOM要素数が爆発してレンダリングが重くなる
1マスごとに列ヘッダーやグリッド背景を生成すると、100件のチケット × 1ヶ月(160スロット)でグリッドのセルとバーの計算量が跳ね上がり、スクロール追従がガタガタになります。
2. 解決策: 「午前・午後(半日スケール)」という絶妙な落とし所
そこで導き出したのが、「午前(〜12:00)」と「午後(12:00〜)」の2スロット(0.5日刻み) という設計です。
+-------------------------------------------------------------+
| 2026年9月 |
+------------------------------+------------------------------+
| 14 (月) | 15 (火) |
+--------------+---------------+--------------+---------------+
| 前 | 後 | 前 | 後 |
+==============+===============+==============+===============+
| [=== タスクA (午前締) ===] | |
| [============== タスクB (終日) =============] |
| | [=== タスクC (午後着手) =====] |
なぜ「半日」がベストなのか?
-
幅がコンパクト(鳥瞰性をキープ):
- 1スロット
32px、1日64px。 - 2週間のスプリント(14日分)でも 約896px。一般的なPC画面にスクロールなしで収まります。
- 1スロット
-
現場の要件の9割をカバーできる:
- 「午前中必着」「昼までにレビュー」「午後から着手」「夕方締め切り」。
- 人間の業務サイクルの大部分は「午前」か「午後」で区切られており、半日精度があれば締め切り遅延の早期検知には十分です。
3. アーキテクチャと処理フロー
チケットデータ(開始日・期日・期日時刻)から、タイムライン上のピクセル座標を算出するパイプラインは以下の通りです。
4. React + Tailwind CSS による実装のポイント
ポイント①: 期日時間に応じたバーの幅・終了位置の計算
チケットの開始日(start_date)と期日(due_date)、および時刻(due_time)から、X座標を正確に計算します。
// 1日あたりのピクセル幅
const dayCellWidth = timeScale === 'halfDay' ? 64 : timeScale === 'day' ? 42 : 14;
// 日付文字列からグリッド上のピクセルX座標を計算
const getXPosition = (dateStr: string | null | undefined): number | null => {
if (!dateStr) return null;
const target = new Date(dateStr);
const start = new Date(minDateStr);
const diffDays = Math.round((target.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
return diffDays * dayCellWidth;
};
// チケットバーのスタイル計算
const getBarStyle = (ticket: Ticket) => {
// 正午(12:00)以前なら午前締め切りと判定
const isMorningDue = Boolean(ticket.due_time && ticket.due_time.trim() <= '12:00');
const startX = getXPosition(startStr) ?? 0;
const endBaseX = getXPosition(endStr) ?? startX;
// 🔴 ここがキモ:
// 午前締めの場合は、その日の前半(dayCellWidth * 0.5)でバーを止める。
// 午後締めまたは時刻未指定の場合は、その日の終わり(dayCellWidth)まで伸ばす。
const endX = endBaseX + (isMorningDue ? dayCellWidth * 0.5 : dayCellWidth);
const minWidth = isMorningDue ? dayCellWidth * 0.5 : dayCellWidth;
const width = Math.max(endX - startX, minWidth);
return {
left: `${startX}px`,
width: `${width}px`,
isMorningDue,
};
};
工夫した点: 「日」表示モードでも半日精度が活きる
dayCellWidth * 0.5 という比率計算にしているため、「日」スケール(1日42px)に切り替えたときも、午前締めのチケットはバーが日の50%(21px)でピタリと止まります。
スケールを切り替えても「午前締めと午後締めでバーの長さが違う」という直感が崩れません。
ポイント②: 破線グリッドによる認知負荷の低減
午前と午後で2列に分けると、画面上の縦線の数が2倍になります。すべて同じ実線で描いてしまうと、「どこからどこまでが1日なのか」が分からなくなり、目が猛烈に疲れます。
そこで、日の区切りは実線(solid)、午前と午後の区切りは薄い破線(dashed) にしました。
{cellGroups.map((cell, idx) => {
const cellWidth = cell.daysCount * dayCellWidth; // 64px
const halfWidth = cellWidth / 2; // 32px (半日)
if (timeScale === 'halfDay') {
return (
<div
key={idx}
style={{ width: `${cellWidth}px` }}
className="shrink-0 border-r border-slate-200 flex"
>
{/* 午前の列: 右側に薄い破線境界 */}
<div
style={{ width: `${halfWidth}px` }}
className="border-r border-dashed border-slate-200/70 h-full"
/>
{/* 午後の列: 右端は親divの実線ボーダー */}
<div style={{ width: `${halfWidth}px` }} className="h-full" />
</div>
);
}
// ... 日・週表示
})}
人間の脳は無意識に「実線=境界」「破線=内部構造」と認識するため、マスが増えてもスッキリと1日のまとまりを把握できます。
ポイント③: 現在時刻(AM/PM)に連動する「今日インジケーター線」
通常のガントチャートの「今日線」は日の中心(または左端)に引かれますが、半日スケールでは現在時刻が午前か午後かで線の位置を変えています。
<div
style={{
left: `${
timeScale === 'halfDay'
? (new Date().getHours() < 12
? todayX + dayCellWidth * 0.25 // 午前の中心
: todayX + dayCellWidth * 0.75) // 午後の中心
: todayX + dayCellWidth / 2
}px`,
}}
className="absolute top-0 bottom-0 w-0.5 bg-sky-500 z-10 pointer-events-none"
>
<div className="bg-sky-500 text-white text-[9px] font-bold px-1 rounded-full absolute -top-1 -left-3 shadow-xs">
今日
</div>
</div>
午後(12:00以降)になると、青いラインがスッと「後」のスロットに移動します。
これにより、「今日の午前中のタスクはもう終わっていなければいけない」「午後のタスクの時間帯に入っている」という時間感覚が、ガントチャートを見るだけで無意識に刷り込まれます。
5. 実際の動作画面
実装後の画面では、以下のように表示されます。
- 上段: 2026年9月
- 中段: 月 14(今日ハイライト)、火 15...
- 下段: 前 / 後
-
チケットバー:
-
9/15 12:00(午前締め切り)のタスクは、15日の「前」スロットの右端(正午の破線)でピタリと止まる。 -
9/15 18:00や終日タスクは、15日の「後」スロットまで伸びる。 - バーの右側に
12:00などの時刻バッジが控えめに表示される。
-
「1時間刻み」のような狂気的なスクロールバーを出さずに、求めていた「半日精度の締め切り管理」が極めて軽量・直感的に実現できました。
まとめ
プロジェクト管理ツールの機能開発では、「細かく管理したいというユーザー要望」と「全体を見通す鳥瞰性・軽快さ」のトレードオフに常に直面します。
- 1時間単位のガントチャート:精度は高いが、横スクロール地獄で使い物にならない。
- 1日単位のガントチャート:全体は見渡せるが、午前締めのタスクが埋没する。
- 午前・午後の半日スケール:鳥瞰性を完全に保ったまま、業務締め切りの9割を正確に視覚化できる。
「日単位ガントチャートに時間を書き込む運用」に疲れている開発者やチームリーダーの方にとって、本設計が一つの参考になれば幸いです。
実際に動くデモで試せます
この記事で紹介した午前・午後の半日スケール対応ガントチャートは、開発中のタスク管理ツール Taski(タスキ) に実装されており、ブラウザ上ですぐに触ることができます。
会員登録やログインは不要で、1秒でお試しデモ環境が立ち上がります。ぜひツールバーの [午前/午後] スケール切り替えや、チケットの締め切り時間によるバーの吸着を実際に体験してみてください!