Show daily streak count on leaderboard as well
This commit is contained in:
@@ -1,51 +1,11 @@
|
|||||||
from flask import Blueprint, render_template
|
from flask import Blueprint, render_template
|
||||||
from app.auth import login_required, get_current_user
|
from app.auth import login_required, get_current_user
|
||||||
from app.db import query, query_one
|
from app.db import query, query_one
|
||||||
from app import SYDNEY_TZ
|
from app.utils import calculate_streak
|
||||||
from datetime import datetime, timezone, timedelta
|
|
||||||
|
|
||||||
bp = Blueprint("dashboard", __name__)
|
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("/")
|
@bp.route("/")
|
||||||
@login_required
|
@login_required
|
||||||
def index():
|
def index():
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from flask import Blueprint, render_template
|
|||||||
from app.auth import login_required
|
from app.auth import login_required
|
||||||
from app.db import query, query_one
|
from app.db import query, query_one
|
||||||
from app import SYDNEY_TZ
|
from app import SYDNEY_TZ
|
||||||
|
from app.utils import calculate_streak
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
|
|
||||||
bp = Blueprint("leaderboard", __name__)
|
bp = Blueprint("leaderboard", __name__)
|
||||||
@@ -46,11 +47,13 @@ def index():
|
|||||||
total_to_lose = start_w - goal
|
total_to_lose = start_w - goal
|
||||||
goal_progress = min(100, round((weight_lost / total_to_lose) * 100, 1)) if total_to_lose > 0 else 0
|
goal_progress = min(100, round((weight_lost / total_to_lose) * 100, 1)) if total_to_lose > 0 else 0
|
||||||
|
|
||||||
|
streak = calculate_streak(u["id"])
|
||||||
ranked.append({
|
ranked.append({
|
||||||
**u,
|
**u,
|
||||||
"weight_lost": round(weight_lost, 1),
|
"weight_lost": round(weight_lost, 1),
|
||||||
"pct_lost": pct_lost,
|
"pct_lost": pct_lost,
|
||||||
"goal_progress": goal_progress,
|
"goal_progress": goal_progress,
|
||||||
|
"streak": streak["current"],
|
||||||
})
|
})
|
||||||
|
|
||||||
# Sort by % lost (descending)
|
# Sort by % lost (descending)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
<th>Lost (%)</th>
|
<th>Lost (%)</th>
|
||||||
<th>Goal Progress</th>
|
<th>Goal Progress</th>
|
||||||
<th>Check-ins</th>
|
<th>Check-ins</th>
|
||||||
|
<th>Streak</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -96,6 +97,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ u.total_checkins }}</td>
|
<td>{{ u.total_checkins }}</td>
|
||||||
|
<td>{% if u.streak > 0 %}<span style="color: var(--warning);">🔥 {{ u.streak }}d</span>{% else
|
||||||
|
%}<span style="color: var(--text-muted);">—</span>{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
42
app/utils.py
Normal file
42
app/utils.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from app.db import query
|
||||||
|
from app import SYDNEY_TZ
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
|
||||||
|
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}
|
||||||
Reference in New Issue
Block a user