プロジェクト横断のタスク一覧と日別集計グラフを追加

左ペインに「すべてのプロジェクト」を追加し、全プロジェクトのタスクを1つの
一覧で扱えるようにした。あわせて右ペインに日別集計タブを設けた。

- /api/all: 全プロジェクトのタスクを開始日時順に返す (CSVはプロジェクト列付き)
- /api/daily: ターンをローカル日付で集計し、活動のない日も0で埋めて返す
  (プロジェクト別の内訳・期間指定 days= / 全期間・横持ちCSV)
- 日別集計タブ: 作業時間の棒グラフ (横断時はプロジェクト別の積み上げ、
  上位7件+その他)、14/30/90日・全期間の切替、日別テーブル、SVG手書きで
  外部ライブラリなし
- 再読込で横断モードの選択が先頭プロジェクトに戻る問題を修正
  (未選択と横断モードを state.project の undefined / null で区別)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 10:08:59 +09:00
co-authored by Claude Opus 5
parent cf17cf3fa0
commit 2fd0ed1bef
5 changed files with 495 additions and 23 deletions
+51
View File
@@ -72,6 +72,7 @@ main { flex: 1; display: flex; min-height: 0; }
border-bottom: 1px solid var(--line);
}
.pane-head > span:first-child { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#detail-title { text-transform: none; } /* タイトルやパスをそのまま出す */
#filter {
flex: 1 1 120px;
min-width: 80px;
@@ -162,3 +163,53 @@ table { width: 100%; border-collapse: collapse; }
.bar .b-idle { background: var(--idle); }
.bar .b-wait { background: var(--user); }
.bar .b-work { background: var(--work); }
/* プロジェクト横断モード */
#project-list li.all { border-bottom: 2px solid var(--line); }
#project-list li.all .p-path::before { content: "▦ "; color: var(--dim); }
.s-proj { color: var(--dim); font-size: 10.5px; margin-bottom: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 日別集計 */
.tabs { display: flex; gap: 4px; flex: none; }
.tab { padding: 2px 9px; font-size: 11px; letter-spacing: normal; text-transform: none; }
.tab.sel { color: var(--accent); border-color: var(--accent); }
.chart-bar {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 14px 0;
flex-wrap: wrap;
}
.rngs { display: flex; gap: 4px; }
.rng { font-size: 11px; padding: 2px 8px; }
.rng.sel { color: var(--accent); border-color: var(--accent); }
.chart-bar .note { color: var(--dim); font-size: 10.5px; }
.chart { padding: 4px 14px 0; overflow-x: auto; }
.chart svg text.ax { fill: var(--dim); font-size: 10px; }
.chart svg text.ax.we { fill: var(--warn); }
.chart svg line.grid { stroke: var(--line); stroke-width: 1; }
.chart svg line.axis { stroke: var(--dim); stroke-width: 1; opacity: .5; }
.chart svg rect.wk { fill: #ffffff; opacity: .028; }
.chart svg rect.hit { fill: transparent; }
.chart svg rect.hit:hover { fill: #ffffff; opacity: .06; }
.legend { display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 8px 14px 2px; font-size: 11px; color: var(--dim); }
.legend .lg { display: flex; align-items: center; gap: 5px; white-space: nowrap; }
.legend .lg i, .pj-i i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; flex: none; }
.legend .lg b { color: var(--fg); font-weight: 500; }
#daily-table { margin-top: 8px; }
#daily-table th {
position: sticky; top: 0; background: var(--pane); border-bottom: 1px solid var(--line);
color: var(--dim); font-weight: 500; font-size: 11px; text-align: left; padding: 6px 10px; white-space: nowrap; z-index: 1;
}
#daily-table td { padding: 5px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
#daily-table tr:hover td { background: var(--pane2); }
#daily-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
#daily-table .time { font-variant-numeric: tabular-nums; white-space: nowrap; }
#daily-table .time.we { color: var(--warn); }
#daily-table .d { color: var(--dim); font-size: 11px; }
#daily-table .pj { display: flex; flex-wrap: wrap; gap: 3px 12px; font-size: 11px; color: var(--dim); }
.pj-i { display: flex; align-items: center; gap: 4px; white-space: nowrap; }