diff --git a/web/src/padelnomics/api.py b/web/src/padelnomics/api.py index 37c620d..8db5bae 100644 --- a/web/src/padelnomics/api.py +++ b/web/src/padelnomics/api.py @@ -8,7 +8,7 @@ daily when the pipeline runs). from quart import Blueprint, abort, jsonify from .analytics import fetch_analytics -from .core import is_flag_enabled +from .core import fetch_all, is_flag_enabled bp = Blueprint("api", __name__) @@ -59,6 +59,20 @@ async def country_cities(country_slug: str): """, [country_slug], ) + # Check which cities have published articles (any language). + article_rows = await fetch_all( + """SELECT url_path FROM articles + WHERE url_path LIKE ? AND status = 'published' + AND published_at <= datetime('now')""", + (f"/markets/{country_slug}/%",), + ) + article_slugs = set() + for a in article_rows: + parts = a["url_path"].rstrip("/").split("/") + if len(parts) >= 4: + article_slugs.add(parts[3]) + for row in rows: + row["has_article"] = row["city_slug"] in article_slugs return jsonify(rows), 200, _CACHE_HEADERS diff --git a/web/src/padelnomics/static/js/article-maps.js b/web/src/padelnomics/static/js/article-maps.js index 5824b27..57fa3b6 100644 --- a/web/src/padelnomics/static/js/article-maps.js +++ b/web/src/padelnomics/static/js/article-maps.js @@ -43,18 +43,23 @@ data.forEach(function(c) { if (!c.lat || !c.lon) return; var size = 10 + 36 * Math.sqrt((c.padel_venue_count || 1) / maxV); - var color = scoreColor(c.market_score); + var hasArticle = c.has_article !== false; + var color = hasArticle ? scoreColor(c.market_score) : '#9CA3AF'; var pop = c.population >= 1000000 ? (c.population / 1000000).toFixed(1) + 'M' : (c.population >= 1000 ? Math.round(c.population / 1000) + 'K' : (c.population || '')); var tip = '' + c.city_name + '
' + (c.padel_venue_count || 0) + ' venues' - + (pop ? ' · ' + pop : '') + '
' - + 'Score ' + Math.round(c.market_score) + '/100'; - L.marker([c.lat, c.lon], { icon: makeIcon(size, color) }) + + (pop ? ' · ' + pop : ''); + if (hasArticle) { + tip += '
Score ' + Math.round(c.market_score) + '/100'; + } + var marker = L.marker([c.lat, c.lon], { icon: makeIcon(size, color) }) .bindTooltip(tip, { className: 'map-tooltip', direction: 'top', offset: [0, -Math.round(size / 2)] }) - .on('click', function() { window.location = '/' + lang + '/markets/' + slug + '/' + c.city_slug; }) .addTo(map); + if (hasArticle) { + marker.on('click', function() { window.location = '/' + lang + '/markets/' + slug + '/' + c.city_slug; }); + } bounds.push([c.lat, c.lon]); }); if (bounds.length) map.fitBounds(bounds, { padding: [24, 24] });