Add sort dropdown for admin geo stats (minutes vs listeners)
- Add dropdown to admin template to choose sort order - Update get-geo-stats to accept order-by parameter - Update API endpoint to pass sort-by parameter - Update ParenScript to read dropdown and pass to API - Default sort is by minutes (engagement time)
This commit is contained in:
parent
39e8f12fe9
commit
52faccea50
|
|
@ -1403,10 +1403,11 @@
|
||||||
("avg_session_minutes" . ,(sixth row))))
|
("avg_session_minutes" . ,(sixth row))))
|
||||||
stats))))))
|
stats))))))
|
||||||
|
|
||||||
(define-api asteroid/stats/geo (&optional (days "7")) ()
|
(define-api asteroid/stats/geo (&optional (days "7") (sort-by "minutes")) ()
|
||||||
"Get geographic distribution of listeners (admin only)"
|
"Get geographic distribution of listeners (admin only).
|
||||||
|
SORT-BY can be 'minutes' (default) or 'listeners'."
|
||||||
(require-role :admin)
|
(require-role :admin)
|
||||||
(let ((stats (get-geo-stats (parse-integer days :junk-allowed t))))
|
(let ((stats (get-geo-stats (parse-integer days :junk-allowed t) sort-by)))
|
||||||
(api-output `(("status" . "success")
|
(api-output `(("status" . "success")
|
||||||
("geo" . ,(mapcar (lambda (row)
|
("geo" . ,(mapcar (lambda (row)
|
||||||
`(("country_code" . ,(first row))
|
`(("country_code" . ,(first row))
|
||||||
|
|
|
||||||
|
|
@ -369,17 +369,21 @@
|
||||||
(log:error "Failed to get daily stats: ~a" e)
|
(log:error "Failed to get daily stats: ~a" e)
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
(defun get-geo-stats (&optional (days 7))
|
(defun get-geo-stats (&optional (days 7) (order-by "minutes"))
|
||||||
"Get geographic distribution for the last N days"
|
"Get geographic distribution for the last N days.
|
||||||
|
ORDER-BY can be 'minutes' (default) or 'listeners'."
|
||||||
(handler-case
|
(handler-case
|
||||||
(with-db
|
(with-db
|
||||||
(postmodern:query
|
(let ((order-column (if (string= order-by "listeners")
|
||||||
(format nil "SELECT country_code, SUM(listener_count) as total_listeners, SUM(listen_minutes) as total_minutes
|
"total_listeners"
|
||||||
|
"total_minutes")))
|
||||||
|
(postmodern:query
|
||||||
|
(format nil "SELECT country_code, SUM(listener_count) as total_listeners, SUM(listen_minutes) as total_minutes
|
||||||
FROM listener_geo_stats
|
FROM listener_geo_stats
|
||||||
WHERE date > NOW() - INTERVAL '~a days'
|
WHERE date > NOW() - INTERVAL '~a days'
|
||||||
GROUP BY country_code
|
GROUP BY country_code
|
||||||
ORDER BY total_minutes DESC
|
ORDER BY ~a DESC
|
||||||
LIMIT 20" days)))
|
LIMIT 20" days order-column))))
|
||||||
(error (e)
|
(error (e)
|
||||||
(log:error "Failed to get geo stats: ~a" e)
|
(log:error "Failed to get geo stats: ~a" e)
|
||||||
nil)))
|
nil)))
|
||||||
|
|
|
||||||
|
|
@ -970,8 +970,10 @@
|
||||||
|
|
||||||
;; Refresh geo stats from API
|
;; Refresh geo stats from API
|
||||||
(defun refresh-geo-stats ()
|
(defun refresh-geo-stats ()
|
||||||
(ps:chain
|
(let* ((sort-select (ps:chain document (get-element-by-id "geo-sort-by")))
|
||||||
(fetch "/api/asteroid/stats/geo?days=7")
|
(sort-by (if sort-select (ps:@ sort-select value) "minutes")))
|
||||||
|
(ps:chain
|
||||||
|
(fetch (+ "/api/asteroid/stats/geo?days=7&sort-by=" sort-by))
|
||||||
(then (lambda (response) (ps:chain response (json))))
|
(then (lambda (response) (ps:chain response (json))))
|
||||||
(then (lambda (result)
|
(then (lambda (result)
|
||||||
(let ((data (or (ps:@ result data) result))
|
(let ((data (or (ps:@ result data) result))
|
||||||
|
|
@ -1009,7 +1011,7 @@
|
||||||
(let ((tbody (ps:chain document (get-element-by-id "geo-stats-body"))))
|
(let ((tbody (ps:chain document (get-element-by-id "geo-stats-body"))))
|
||||||
(when tbody
|
(when tbody
|
||||||
(setf (ps:@ tbody inner-h-t-m-l)
|
(setf (ps:@ tbody inner-h-t-m-l)
|
||||||
"<tr><td colspan=\"3\" style=\"color: #ff6666;\">Error loading geo data</td></tr>")))))))
|
"<tr><td colspan=\"3\" style=\"color: #ff6666;\">Error loading geo data</td></tr>"))))))))
|
||||||
|
|
||||||
;; Toggle city display for a country
|
;; Toggle city display for a country
|
||||||
(defun toggle-country-cities (country)
|
(defun toggle-country-cities (country)
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,13 @@
|
||||||
|
|
||||||
<!-- Geo Stats -->
|
<!-- Geo Stats -->
|
||||||
<h3 style="margin-top: 20px;">🌍 Listener Locations (Last 7 Days)</h3>
|
<h3 style="margin-top: 20px;">🌍 Listener Locations (Last 7 Days)</h3>
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<label for="geo-sort-by">Sort by: </label>
|
||||||
|
<select id="geo-sort-by" class="sort-select" onchange="refreshGeoStats()">
|
||||||
|
<option value="minutes" selected>Minutes Listened</option>
|
||||||
|
<option value="listeners">Unique Listeners</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div id="geo-stats-container">
|
<div id="geo-stats-container">
|
||||||
<table class="listener-stats-table" id="geo-stats-table">
|
<table class="listener-stats-table" id="geo-stats-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue