diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index 944c603..9c5f0d3 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -1,10 +1,51 @@ from flask import Blueprint, render_template from app.auth import login_required, get_current_user from app.db import query, query_one +from app import SYDNEY_TZ +from datetime import datetime, timezone, timedelta bp = Blueprint("dashboard", __name__) +def calculate_streak(user_id): + """Calculate current and best consecutive-day check-in streaks.""" + rows = query( + """SELECT DISTINCT (checked_in_at AT TIME ZONE 'UTC' AT TIME ZONE 'Australia/Sydney')::date AS d + FROM checkins WHERE user_id = %s ORDER BY d DESC""", + (user_id,), + ) + if not rows: + return {"current": 0, "best": 0} + + days = [r["d"] for r in rows] + today = datetime.now(SYDNEY_TZ).date() + + # Current streak: must include today or yesterday to count + current = 0 + expected = today + if days[0] == today or days[0] == today - timedelta(days=1): + expected = days[0] + for d in days: + if d == expected: + current += 1 + expected -= timedelta(days=1) + else: + break + + # Best streak + best = 1 + run = 1 + for i in range(1, len(days)): + if days[i] == days[i - 1] - timedelta(days=1): + run += 1 + best = max(best, run) + else: + run = 1 + + best = max(best, current) + return {"current": current, "best": best} + + @bp.route("/") @login_required def index(): @@ -59,6 +100,9 @@ def index(): (user["id"],), ) + # Streak + streak = calculate_streak(user["id"]) + return render_template( "dashboard.html", user=user, @@ -69,4 +113,6 @@ def index(): recent_checkins=recent_checkins, activity=activity, milestones=milestones, + streak=streak, ) + diff --git a/app/static/css/style.css b/app/static/css/style.css index f262db2..9985dc8 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -294,6 +294,11 @@ body { opacity: 1; } +.stat-card-streak::before { + background: linear-gradient(135deg, #f59e0b, #ef4444); + opacity: 1; +} + .stat-label { font-size: 0.75rem; font-weight: 500; @@ -737,6 +742,42 @@ tr:hover td { border-color: rgba(239, 68, 68, 0.2); } +.btn-icon-success:hover { + color: var(--success) !important; + background: var(--success-bg) !important; + border-color: rgba(16, 185, 129, 0.2) !important; +} + +/* ========== INLINE EDIT ========== */ +.checkin-actions { + display: flex; + gap: 0.25rem; + align-items: center; +} + +.edit-input { + width: 100%; + max-width: 120px; + padding: 0.35rem 0.6rem; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: inherit; + font-size: 0.85rem; + transition: all 0.2s ease; + outline: none; +} + +.edit-input:focus { + border-color: var(--border-focus); + box-shadow: 0 0 0 2px var(--accent-glow); +} + +.editing-row td { + background: var(--bg-card-hover); +} + /* ========== EMPTY STATE ========== */ .empty-state { text-align: center; @@ -933,20 +974,46 @@ tr:hover td { } .container { - padding: 1rem; + padding: 0.75rem; } .stats-grid { grid-template-columns: repeat(2, 1fr); + gap: 0.6rem; + margin-bottom: 1rem; + } + + .stat-card { + padding: 0.85rem; + } + + .stat-value { + font-size: 1.35rem; + } + + .stat-label { + font-size: 0.65rem; + margin-bottom: 0.25rem; } .charts-grid { grid-template-columns: 1fr; + gap: 0.75rem; + margin-bottom: 1rem; + } + + .chart-container { + height: 220px; } .grid-2, .grid-3 { grid-template-columns: 1fr; + gap: 0.75rem; + } + + .card { + padding: 1rem; } .form-inline { @@ -958,12 +1025,16 @@ tr:hover td { margin-bottom: 0.75rem; } - .page-header h1 { - font-size: 1.4rem; + .page-header { + margin-bottom: 1rem; } - .stat-value { - font-size: 1.4rem; + .page-header h1 { + font-size: 1.3rem; + } + + .page-header p { + font-size: 0.8rem; } .auth-card { @@ -998,10 +1069,27 @@ tr:hover td { .filter-group-people { min-width: unset; } + + .card-header h2 { + font-size: 0.9rem; + } + + .activity-item { + padding: 0.5rem 0; + } } @media (max-width: 480px) { .stats-grid { - grid-template-columns: 1fr; + grid-template-columns: repeat(2, 1fr); + gap: 0.5rem; + } + + .stat-card { + padding: 0.7rem; + } + + .stat-value { + font-size: 1.15rem; } } \ No newline at end of file diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 0db53f6..73867ba 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -34,9 +34,11 @@