feat: add 10 UX improvements from interface review
- Stale data banner after 3 consecutive fetch failures, auto-clears on recovery - Date navigation with left/right arrows (Yesterday/Today/Tomorrow labels), fetches from NHL API for non-today dates, disables auto-refresh on history - Empty state message when no games are scheduled - Series detail page auto-refreshes every 30s when a game is live - Notification permission deferred until a playoff OT actually occurs - Scroll position saved/restored when navigating to/from series detail - Team records rendered with better contrast and tabular nums - Active bracket round highlighted with gold heading + underline, completed rounds dimmed more aggressively, mobile accordion auto-opens current round - Browser tab title shows live game count (e.g. "NHL Scoreboard (3 Live)") - Service worker update shows a dismissable toast instead of force-reloading Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+36
-10
@@ -1,6 +1,8 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from flask import abort, make_response, render_template, jsonify, send_from_directory
|
||||
import requests as http_requests
|
||||
from flask import abort, make_response, render_template, jsonify, request, send_from_directory
|
||||
|
||||
from app import APP_VERSION, app, static_v
|
||||
from app.config import SCOREBOARD_DATA_FILE
|
||||
@@ -19,6 +21,18 @@ from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
_EASTERN = ZoneInfo("America/New_York")
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _fetch_date(date_str):
|
||||
url = f"https://api-web.nhle.com/v1/score/{date_str}"
|
||||
try:
|
||||
resp = http_requests.get(url, timeout=10)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except http_requests.RequestException as e:
|
||||
_logger.error("Failed to fetch scores for %s: %s", date_str, e)
|
||||
return None
|
||||
|
||||
|
||||
def _max_playoff_round(raw_games):
|
||||
@@ -69,15 +83,27 @@ def index():
|
||||
|
||||
@app.route("/scoreboard")
|
||||
def get_scoreboard():
|
||||
try:
|
||||
with open(SCOREBOARD_DATA_FILE, "r") as json_file:
|
||||
scoreboard_data = json.load(json_file)
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "Failed to retrieve scoreboard data. File not found."})
|
||||
except json.JSONDecodeError:
|
||||
return jsonify(
|
||||
{"error": "Failed to retrieve scoreboard data. Invalid JSON format."}
|
||||
)
|
||||
date_param = request.args.get("date")
|
||||
today_str = datetime.now(_EASTERN).strftime("%Y-%m-%d")
|
||||
|
||||
if date_param and date_param != today_str:
|
||||
try:
|
||||
datetime.strptime(date_param, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return jsonify({"error": "Invalid date format. Use YYYY-MM-DD."}), 400
|
||||
scoreboard_data = _fetch_date(date_param)
|
||||
if not scoreboard_data:
|
||||
return jsonify({"error": "Failed to retrieve scoreboard data for that date."})
|
||||
else:
|
||||
try:
|
||||
with open(SCOREBOARD_DATA_FILE, "r") as json_file:
|
||||
scoreboard_data = json.load(json_file)
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "Failed to retrieve scoreboard data. File not found."})
|
||||
except json.JSONDecodeError:
|
||||
return jsonify(
|
||||
{"error": "Failed to retrieve scoreboard data. Invalid JSON format."}
|
||||
)
|
||||
|
||||
if scoreboard_data:
|
||||
raw_games = scoreboard_data.get("games", [])
|
||||
|
||||
Reference in New Issue
Block a user