diff --git a/src/web.rs b/src/web.rs
index aa8cabe..9feb35a 100644
--- a/src/web.rs
+++ b/src/web.rs
@@ -50,9 +50,7 @@ pub async fn index(
_ => None,
};
- let groups = leaderboard::build(&state.db, valid_group_id)
- .await
- .unwrap_or_default();
+ let groups = Vec::new();
let available_groups = match current.as_ref() {
Some((id, _)) => futures_groups(&state, *id).await.unwrap_or_default(),
@@ -419,10 +417,12 @@ fn render_index(
if groups.is_empty() {
html.push_str(
- r#"
Noch keine Landkreisüberquerungen für diese Auswahl.
"#,
+ r#"Landkreisübertritte werden geladen …
"#,
);
}
+ html.push_str(r#""#);
+
for group in groups {
html.push_str("");
html.push_str(&format!(
@@ -711,6 +711,38 @@ document.querySelectorAll('[data-time]').forEach(el=>{
el.textContent=fmtTime(el.dataset.time);
el.title=Intl.DateTimeFormat().resolvedOptions().timeZone;
});
+function escapeHtml(value){return String(value).replace(/[&<>\"']/g,ch=>({'&':'&','<':'<','>':'>','\"':'"',"'":'''}[ch]));}
+function formatPower(value){return value == null ? '—' : Number(value).toFixed(0)+' W';}
+function formatHr(value){return value == null ? '—' : Number(value).toFixed(0)+' bpm';}
+function leaderboardRowHtml(row){
+ const interval=row.interval_url ? 'Intervall' : '—';
+ return '| '+row.rank+' | '+escapeHtml(row.athlete)+' | '+escapeHtml(row.crossing_time)+' | '+escapeHtml(row.activity_id)+' | '+interval+' | '+formatPower(row.average_watts)+' | '+formatHr(row.average_heartrate)+' | |
';
+}
+function leaderboardGroupKey(group){return group.bucket_start+'|'+group.from_county+'|'+group.to_county;}
+function leaderboardGroupHtml(group){return ''+escapeHtml(group.from_county)+' → '+escapeHtml(group.to_county)+'
10-Minuten-Fenster · '+escapeHtml(group.bucket_start)+' · Standort ±10 m
| # | Fahrer | Übertritt | Aktivität | Intervall | Power | HR | Karte |
'+group.rows.map(leaderboardRowHtml).join('')+'
';}
+function initLeaderboardElements(root){
+ root.querySelectorAll('[data-time]').forEach(el=>{el.textContent=fmtTime(el.dataset.time);el.title=Intl.DateTimeFormat().resolvedOptions().timeZone;});
+ root.querySelectorAll('.group-map').forEach(el=>{if(el.dataset.mapReady)return;const lat=Number(el.dataset.lat),lon=Number(el.dataset.lon);if(!Number.isFinite(lat)||!Number.isFinite(lon))return;el.dataset.mapReady='1';const map=L.map(el,{scrollWheelZoom:false}).setView([lat,lon],15);tileLayer(map);L.circle([lat,lon],{radius:10}).addTo(map);L.marker([lat,lon]).addTo(map);});
+ root.querySelectorAll('.mini-map').forEach(el=>{if(el.dataset.mapReady)return;const lat=Number(el.dataset.lat),lon=Number(el.dataset.lon);if(!Number.isFinite(lat)||!Number.isFinite(lon))return;el.dataset.mapReady='1';const map=L.map(el,{zoomControl:false,dragging:false,scrollWheelZoom:false,doubleClickZoom:false,touchZoom:false}).setView([lat,lon],16);tileLayer(map);L.circle([lat,lon],{radius:10}).addTo(map);L.marker([lat,lon]).addTo(map);});
+}
+const leaderboardList=document.getElementById('leaderboard-list');
+if(leaderboardList){
+ const status=document.getElementById('leaderboard-status'),sentinel=document.getElementById('leaderboard-sentinel'),groups=new Map();
+ let offset=0,loading=false,hasMore=true; const pageSize=50;
+ async function loadLeaderboard(){
+ if(loading||!hasMore)return; loading=true;
+ try{
+ const params=new URLSearchParams({offset:String(offset),limit:String(pageSize)}),selected=new URLSearchParams(location.search).get('group_id');
+ if(selected)params.set('group_id',selected);
+ const response=await fetch('/api/leaderboard?'+params,{headers:{Accept:'application/json'}}); if(!response.ok)throw new Error('HTTP '+response.status);
+ const page=await response.json();
+ if(offset===0&&page.groups.length===0){status.textContent='Noch keine Landkreisüberquerungen für diese Auswahl.';status.style.display='block';}else{status.style.display='none';}
+ for(const group of page.groups){const key=leaderboardGroupKey(group),existing=groups.get(key);if(existing){const tbody=existing.querySelector('tbody');tbody.insertAdjacentHTML('beforeend',group.rows.map(leaderboardRowHtml).join(''));initLeaderboardElements(tbody);}else{leaderboardList.insertAdjacentHTML('beforeend',leaderboardGroupHtml(group));const section=leaderboardList.lastElementChild;groups.set(key,section);initLeaderboardElements(section);}}
+ offset=page.next_offset;hasMore=page.has_more;if(!hasMore)sentinel.style.display='none';
+ }catch(error){console.error(error);status.textContent='Landkreisübertritte konnten nicht geladen werden.';status.style.display='block';}finally{loading=false;}
+ }
+ const observer=new IntersectionObserver(entries=>{if(entries.some(entry=>entry.isIntersecting))loadLeaderboard();},{rootMargin:'800px 0px'});observer.observe(sentinel);loadLeaderboard();
+}
document.querySelectorAll('.group-map').forEach(el=>{
const lat=Number(el.dataset.lat),lon=Number(el.dataset.lon);
if(!Number.isFinite(lat)||!Number.isFinite(lon)) return;