fix: adds timeline bars to SLM calendar view, more conscise and legible.
This commit is contained in:
@@ -1235,36 +1235,83 @@ async def get_sessions_calendar(
|
||||
if loc:
|
||||
loc_names[lid] = loc.name
|
||||
|
||||
# Build day -> list of session dots
|
||||
# day key: date object
|
||||
# Build calendar grid bounds first (needed for session spanning logic)
|
||||
first_day = _date(year, month, 1)
|
||||
last_day = _date(year, month, monthrange(year, month)[1])
|
||||
days_before = (first_day.isoweekday() % 7)
|
||||
grid_start = first_day - _td(days=days_before)
|
||||
days_after = 6 - (last_day.isoweekday() % 7)
|
||||
grid_end = last_day + _td(days=days_after)
|
||||
|
||||
def _period_hours(s):
|
||||
"""Return (start_hour, end_hour) for a session, falling back to period_type defaults."""
|
||||
psh, peh = s.period_start_hour, s.period_end_hour
|
||||
if psh is None or peh is None:
|
||||
if s.period_type and "night" in s.period_type:
|
||||
return 19, 7
|
||||
if s.period_type and "day" in s.period_type:
|
||||
return 7, 19
|
||||
return psh, peh
|
||||
|
||||
# Build day -> list of gantt segments
|
||||
day_sessions: dict = {}
|
||||
for s in sessions:
|
||||
if not s.started_at:
|
||||
continue
|
||||
local_start = utc_to_local(s.started_at)
|
||||
d = local_start.date()
|
||||
if d.year == year and d.month == month:
|
||||
if d not in day_sessions:
|
||||
day_sessions[d] = []
|
||||
day_sessions[d].append({
|
||||
"session_id": s.id,
|
||||
"label": s.session_label or f"Session {s.id[:8]}",
|
||||
"location_id": s.location_id,
|
||||
"location_name": loc_names.get(s.location_id, "Unknown"),
|
||||
"color": loc_color.get(s.location_id, "#9ca3af"),
|
||||
"status": s.status,
|
||||
"period_type": s.period_type,
|
||||
})
|
||||
local_end = utc_to_local(s.stopped_at) if s.stopped_at else now_local
|
||||
span_start = local_start.date()
|
||||
span_end = local_end.date()
|
||||
psh, peh = _period_hours(s)
|
||||
|
||||
# Build calendar grid (Mon–Sun weeks)
|
||||
first_day = _date(year, month, 1)
|
||||
last_day = _date(year, month, monthrange(year, month)[1])
|
||||
# Start on Sunday before first_day (isoweekday: Mon=1 … Sun=7; weekday: Mon=0 … Sun=6)
|
||||
days_before = (first_day.isoweekday() % 7) # Sun=0, Mon=1, …, Sat=6
|
||||
grid_start = first_day - _td(days=days_before)
|
||||
# End on Saturday after last_day
|
||||
days_after = 6 - (last_day.isoweekday() % 7)
|
||||
grid_end = last_day + _td(days=days_after)
|
||||
cur_d = span_start
|
||||
while cur_d <= span_end:
|
||||
if grid_start <= cur_d <= grid_end:
|
||||
# Device bar bounds (hours 0–24 within this day)
|
||||
dev_sh = (local_start.hour + local_start.minute / 60.0) if cur_d == span_start else 0.0
|
||||
dev_eh = (local_end.hour + local_end.minute / 60.0) if cur_d == span_end else 24.0
|
||||
|
||||
# Effective window within this day
|
||||
eff_sh = eff_eh = None
|
||||
if psh is not None and peh is not None:
|
||||
if psh < peh:
|
||||
# Day window e.g. 7→19
|
||||
eff_sh, eff_eh = float(psh), float(peh)
|
||||
else:
|
||||
# Night window crossing midnight e.g. 19→7
|
||||
if cur_d == span_start:
|
||||
eff_sh, eff_eh = float(psh), 24.0
|
||||
else:
|
||||
eff_sh, eff_eh = 0.0, float(peh)
|
||||
|
||||
# Format tooltip labels
|
||||
def _fmt_h(h):
|
||||
hh = int(h) % 24
|
||||
mm = int((h % 1) * 60)
|
||||
suffix = "AM" if hh < 12 else "PM"
|
||||
return f"{hh % 12 or 12}:{mm:02d} {suffix}"
|
||||
|
||||
if cur_d not in day_sessions:
|
||||
day_sessions[cur_d] = []
|
||||
day_sessions[cur_d].append({
|
||||
"session_id": s.id,
|
||||
"label": s.session_label or f"Session {s.id[:8]}",
|
||||
"location_id": s.location_id,
|
||||
"location_name": loc_names.get(s.location_id, "Unknown"),
|
||||
"color": loc_color.get(s.location_id, "#9ca3af"),
|
||||
"status": s.status,
|
||||
"period_type": s.period_type,
|
||||
# Gantt bar percentages (0–100 scale across 24 hours)
|
||||
"dev_start_pct": round(dev_sh / 24 * 100, 1),
|
||||
"dev_width_pct": max(1.5, round((dev_eh - dev_sh) / 24 * 100, 1)),
|
||||
"eff_start_pct": round(eff_sh / 24 * 100, 1) if eff_sh is not None else None,
|
||||
"eff_width_pct": max(1.0, round((eff_eh - eff_sh) / 24 * 100, 1)) if eff_sh is not None else None,
|
||||
"dev_start_label": _fmt_h(dev_sh),
|
||||
"dev_end_label": _fmt_h(dev_eh),
|
||||
"eff_start_label": f"{int(psh):02d}:00" if eff_sh is not None else None,
|
||||
"eff_end_label": f"{int(peh):02d}:00" if eff_sh is not None else None,
|
||||
})
|
||||
cur_d += _td(days=1)
|
||||
|
||||
weeks = []
|
||||
cur = grid_start
|
||||
|
||||
Reference in New Issue
Block a user