fix: timeline chart linear scale, date filter on daily chart, null-safe chart init
This commit is contained in:
parent
0ee3c3fbd9
commit
46db7476a3
3 changed files with 29 additions and 13 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)`;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const DashboardPage = (() => {
|
|||
<div class="chart-wrap"><canvas id="chart-donut"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card wide">
|
||||
<h3>Daily Activity (last 30 days)</h3>
|
||||
<h3>Daily Activity</h3>
|
||||
<div class="chart-wrap"><canvas id="chart-daily"></canvas></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
|
|
@ -82,7 +82,7 @@ const DashboardPage = (() => {
|
|||
const [summary, projects, timeline, monthly, dow, tools, activity] = await Promise.all([
|
||||
Api.get('/api/dashboard/summary', params),
|
||||
Api.get('/api/dashboard/projects', params),
|
||||
Api.get('/api/dashboard/timeline', { days: 30 }),
|
||||
Api.get('/api/dashboard/timeline', params),
|
||||
Api.get('/api/dashboard/monthly'),
|
||||
Api.get('/api/dashboard/dow'),
|
||||
Api.get('/api/dashboard/tools', params),
|
||||
|
|
@ -159,9 +159,10 @@ const DashboardPage = (() => {
|
|||
));
|
||||
|
||||
// Timeline (today's sessions)
|
||||
if (activity.length > 0) {
|
||||
const timelineCfg = activity.length > 0 ? ChartDefs.timeline(activity) : null;
|
||||
if (timelineCfg) {
|
||||
document.getElementById('timeline-wrap').style.height = Math.max(160, activity.length * 36) + 'px';
|
||||
makeChart('chart-timeline', ChartDefs.timeline(activity));
|
||||
makeChart('chart-timeline', timelineCfg);
|
||||
} else {
|
||||
const wrap = document.getElementById('timeline-wrap');
|
||||
if (wrap) wrap.innerHTML = '<div class="empty" style="padding:20px">No sessions today</div>';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue