""" FocusTrack - Page 2: Analytics & Reports Date-range charts, heatmaps, export. """ import streamlit as st import plotly.express as px import plotly.graph_objects as go import pandas as pd import json import io from datetime import datetime, timedelta, date from ui_utils import ( fmt_duration, fmt_duration_long, get_focus_score, privacy_badge, page_header, CATEGORY_COLORS, get_db, get_date_range ) def render(): db = get_db() privacy_badge() page_header("Analytics & Reports", "Visualize your productivity trends") # ── Date Range Selector ─────────────────────────────────────────────────── col_range, col_start, col_end = st.columns([1.5, 1, 1]) with col_range: range_sel = st.selectbox( "Date range", ["Today", "Yesterday", "Last 7 days", "Last 30 days", "Custom"], index=2, ) start, end = get_date_range(range_sel) if range_sel == "Custom": with col_start: start_d = st.date_input("From", value=date.today() - timedelta(days=6)) with col_end: end_d = st.date_input("To", value=date.today()) start = datetime.combine(start_d, datetime.min.time()) end = datetime.combine(end_d, datetime.max.time()) # ── Summary Metrics ─────────────────────────────────────────────────────── summary = db.get_summary(start, end) focus_sec = summary.get("focus_seconds") or 0 idle_sec = summary.get("idle_seconds") or 0 total_sec = summary.get("total_seconds") or 0 c1, c2, c3, c4 = st.columns(4) with c1: st.metric("Total Focus Time", fmt_duration_long(focus_sec)) with c2: st.metric("Total Idle Time", fmt_duration_long(idle_sec)) with c3: st.metric("Focus Score", f"{get_focus_score(focus_sec, total_sec)}%") with c4: st.metric("Tracked Time", fmt_duration_long(total_sec)) st.markdown("
", unsafe_allow_html=True) # ── Daily Activity Bar Chart ────────────────────────────────────────────── daily = db.get_daily_totals(start, end) if daily: st.markdown("#### Daily Activity") df_daily = pd.DataFrame(daily) df_daily["focus_min"] = df_daily["focus_seconds"] / 60 df_daily["idle_min"] = df_daily["idle_seconds"] / 60 fig = go.Figure() fig.add_bar( x=df_daily["day"], y=df_daily["focus_min"], name="Focus", marker_color="#6366f1", hovertemplate="%{x}No data for this range.
", unsafe_allow_html=True) with col_r: st.markdown("#### Top Apps Leaderboard") apps_data = db.get_by_app(start, end, limit=10) if apps_data: df_apps = pd.DataFrame(apps_data) df_apps["minutes"] = df_apps["total_seconds"] / 60 html = "" max_min = df_apps["minutes"].max() or 1 for i, row in df_apps.iterrows(): cat = row.get("category", "uncategorized") color = CATEGORY_COLORS.get(cat, "#6366f1") pct = int((row["minutes"] / max_min) * 100) rank = i + 1 html += f"""No data for this range.
", unsafe_allow_html=True) # ── Weekly Heatmap ──────────────────────────────────────────────────────── if range_sel in ("Last 7 days", "Last 30 days", "Custom"): st.markdown("#### Weekly Activity Heatmap (hours × days)") acts = db.get_activities(start, end, limit=50000) if acts: df_all = pd.DataFrame(acts) df_all["dt"] = pd.to_datetime(df_all["timestamp"]) df_all["hour"] = df_all["dt"].dt.hour df_all["day"] = df_all["dt"].dt.strftime("%a %b %d") heatmap = df_all.groupby(["day", "hour"])["duration_seconds"].sum().reset_index() heatmap["minutes"] = heatmap["duration_seconds"] / 60 pivot = heatmap.pivot_table(index="day", columns="hour", values="minutes", fill_value=0) fig_hm = go.Figure(go.Heatmap( z=pivot.values, x=[f"{h:02d}:00" for h in pivot.columns], y=pivot.index.tolist(), colorscale=[[0, "#0a0a0f"], [0.3, "#1a1a3a"], [0.7, "#4040a0"], [1, "#6366f1"]], showscale=True, hovertemplate="%{y} at %{x}No data to export for selected range.
", unsafe_allow_html=True)