From 96edc606d3e1cd720988df6552dd3e0b2cdef0a6 Mon Sep 17 00:00:00 2001 From: Samuel Enocsson Date: Fri, 22 May 2026 13:32:02 +0200 Subject: [PATCH] fix: offer refresh button when round history is empty When a player has rating_history (graph) but no round_history (per-round detail), calculating a target produced a dead-end error. Now the modal detects the NO_ROUNDS case and shows a button that triggers the existing refresh-round-history endpoint and re-runs the calculation on success. Handles the 24h rate-limit and other refresh errors explicitly. --- public/css/players.css | 7 +++++ public/js/players.js | 62 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/public/css/players.css b/public/css/players.css index 41dff4e..458fd77 100644 --- a/public/css/players.css +++ b/public/css/players.css @@ -353,3 +353,10 @@ .target-rating-result .loading { color: var(--text-muted); } + +.target-rating-result .no-history-prompt { + display: flex; + flex-direction: column; + gap: 12px; + align-items: flex-start; +} diff --git a/public/js/players.js b/public/js/players.js index 0867e8e..9a62929 100644 --- a/public/js/players.js +++ b/public/js/players.js @@ -615,6 +615,10 @@ async function calculateTargetRating(event) { clearResult(); if (!response.ok || !data.success) { + if (response.status === 404 && data.errorType === 'NO_ROUNDS') { + renderNoHistoryPrompt(pdgaNumber, result); + return; + } const msg = data.details ? data.error + ': ' + data.details : (data.error || 'Calculation failed'); _targetResultMsg(result, 'error', msg); return; @@ -661,3 +665,61 @@ async function calculateTargetRating(event) { function closeTargetRatingModal(event) { document.getElementById('target-rating-modal').style.display = 'none'; } + +function renderNoHistoryPrompt(pdgaNumber, container) { + const wrapper = document.createElement('div'); + wrapper.className = 'no-history-prompt'; + + const msg = document.createElement('div'); + msg.textContent = 'No round-level history is stored for this player yet. Refresh from PDGA to enable the calculation.'; + wrapper.appendChild(msg); + + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'btn btn-confirm'; + btn.textContent = 'Refresh round history & calculate'; + btn.addEventListener('click', function () { refreshHistoryThenCalculate(pdgaNumber); }); + wrapper.appendChild(btn); + + container.appendChild(wrapper); +} + +async function refreshHistoryThenCalculate(pdgaNumber) { + const result = document.getElementById('target-rating-result'); + while (result.firstChild) result.removeChild(result.firstChild); + _targetResultMsg(result, 'loading', 'Refreshing round history from PDGA — this may take up to 30 seconds...'); + + try { + const response = await fetch('/api/refresh-round-history/' + pdgaNumber, { method: 'POST' }); + const data = await response.json(); + while (result.firstChild) result.removeChild(result.firstChild); + + if (response.status === 429) { + const hours = data.hoursRemaining ? data.hoursRemaining + ' hour(s)' : 'a while'; + _targetResultMsg(result, 'error', 'Round history was refreshed recently. Try again in ' + hours + '.'); + return; + } + + if (!response.ok || !data.success) { + const msg = data.details ? data.error + ': ' + data.details : (data.error || 'Refresh failed'); + _targetResultMsg(result, 'error', msg); + return; + } + + if (data.debugLog) cachedDebugInfo[pdgaNumber] = data.debugLog; + const predictedCell = document.getElementById('predicted-' + pdgaNumber); + if (predictedCell) { + const predictedValue = predictedCell.querySelector('.predicted-value'); + if (predictedValue) { + predictedValue.textContent = data.predictedRating || 'N/A'; + predictedValue.dataset.stddev = data.stdDev || ''; + } + } + + await calculateTargetRating(null); + } catch (err) { + console.error('Error refreshing round history:', err); + while (result.firstChild) result.removeChild(result.firstChild); + _targetResultMsg(result, 'error', 'Network error during refresh. Please try again.'); + } +}