diff --git a/src/routers/dashboard.py b/src/routers/dashboard.py index dc3d765..5147b7f 100644 --- a/src/routers/dashboard.py +++ b/src/routers/dashboard.py @@ -163,11 +163,12 @@ async def projects_overview( @router.get("/timeline", response_model=list[DailyPoint]) async def timeline( user: CurrentUser, - days: int = Query(default=30, ge=7, le=365), + from_date: date | None = Query(default=None, alias="from"), + to_date: date | None = Query(default=None, alias="to"), db: AsyncSession = Depends(get_db), ): - to_date = date.today() - from_date = to_date - timedelta(days=days - 1) + from_date, to_date = _date_range(from_date, to_date) + days = (to_date - from_date).days + 1 result = await db.execute( select(DailyStat.date, func.sum(DailyStat.total_hours), func.sum(DailyStat.session_count)) diff --git a/src/static/js/charts.js b/src/static/js/charts.js index cee29a8..bd47d7d 100644 --- a/src/static/js/charts.js +++ b/src/static/js/charts.js @@ -177,17 +177,26 @@ const ChartDefs = { }, timeline(sessions) { - // Gantt-like bars: each session = one horizontal bar - // Filter out sessions with missing/invalid timestamps + // Gantt-like bars: filter invalid timestamps first sessions = sessions.filter(s => { const st = new Date(s.start_at).getTime(); const en = new Date(s.end_at).getTime(); return st > 0 && en > 0 && !isNaN(st) && !isNaN(en) && en >= st; }); + if (!sessions.length) return null; + const labels = sessions.map(s => s.project_name.substring(0, 20)); const starts = sessions.map(s => new Date(s.start_at).getTime()); const ends = sessions.map(s => new Date(s.end_at).getTime()); const data = sessions.map((s, i) => [starts[i], ends[i]]); + const pad = 15 * 60 * 1000; // 15min padding + const scaleMin = Math.min(...starts) - pad; + const scaleMax = Math.max(...ends) + pad; + + const _fmt = ms => { + const d = new Date(ms); + return d.getHours().toString().padStart(2,'0') + ':' + d.getMinutes().toString().padStart(2,'0'); + }; return { type: 'bar', @@ -206,9 +215,14 @@ const ChartDefs = { indexAxis: 'y', scales: { x: { - type: 'time', - time: { unit: 'hour', displayFormats: { hour: 'HH:mm' } }, + type: 'linear', + min: scaleMin, + max: scaleMax, grid: { color: GRID_COLOR }, + ticks: { + maxTicksLimit: 12, + callback: v => _fmt(v), + }, }, y: { grid: { display: false } }, }, @@ -219,8 +233,8 @@ const ChartDefs = { callbacks: { label: ctx => { const s = sessions[ctx.dataIndex]; - const from = new Date(s.start_at).toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit' }); - const to = new Date(s.end_at).toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit' }); + const from = _fmt(new Date(s.start_at).getTime()); + const to = _fmt(new Date(s.end_at).getTime()); return ` ${from} – ${to} (${s.active_hours.toFixed(1)}h)`; }, }, diff --git a/src/static/js/pages/dashboard.js b/src/static/js/pages/dashboard.js index 2092daf..9bfad99 100644 --- a/src/static/js/pages/dashboard.js +++ b/src/static/js/pages/dashboard.js @@ -40,7 +40,7 @@ const DashboardPage = (() => {