feat: add refresh button to mobile player card (#26)

This commit is contained in:
Samuel Enocsson
2026-06-08 08:24:09 +02:00
parent 8ee5cc3861
commit 16c045e7cc
3 changed files with 36 additions and 4 deletions
+21
View File
@@ -342,6 +342,27 @@
transform: rotate(180deg); transform: rotate(180deg);
} }
/* Refresh button: hidden by default, revealed only when the card is open.
Larger than the desktop icon to give a comfortable touch target (≥44px). */
.m-card__head .m-refresh-icon {
display: none;
}
.m-card.is-open .m-card__head .m-refresh-icon {
display: grid;
width: 44px;
height: 44px;
margin-left: 0;
font-size: 15px;
opacity: 0.7;
flex-shrink: 0;
}
.m-card.is-open .m-card__head .m-refresh-icon:active {
opacity: 1;
color: var(--accent);
}
.m-card__body { .m-card__body {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) auto; grid-template-columns: minmax(0, 1fr) auto;
+10 -4
View File
@@ -131,10 +131,16 @@ async function clearCache() {
// Refreshes both the current rating and the prediction in one click, then // Refreshes both the current rating and the prediction in one click, then
// re-swaps the table so every derived value (deltas, pills, sparkline) reflects // re-swaps the table so every derived value (deltas, pills, sparkline) reflects
// the new state. Cheaper than fine-grained DOM updates and guaranteed consistent // the new state. Cheaper than fine-grained DOM updates and guaranteed consistent
// because the server renders the truth. // because the server renders the truth. The mobile cards partial is included
// inside ratings-table, so swapping #ratings-table re-renders both views at once.
async function refreshPlayerData(pdgaNumber) { async function refreshPlayerData(pdgaNumber) {
const icon = document.querySelector(`#row-${pdgaNumber} .cell-actions .refresh-icon`); // The desktop row exists in the DOM even on mobile (hidden via CSS), so spin
if (icon) icon.classList.add('spinning'); // both possible icons; only the one visible in the active viewport is seen.
const icons = [
document.querySelector(`#row-${pdgaNumber} .cell-actions .refresh-icon`),
document.querySelector(`#m-card-${pdgaNumber} .m-refresh-icon`)
].filter(Boolean);
icons.forEach(icon => icon.classList.add('spinning'));
try { try {
await Promise.allSettled([ await Promise.allSettled([
fetch(`/api/refresh-player/${pdgaNumber}`, { method: 'POST' }), fetch(`/api/refresh-player/${pdgaNumber}`, { method: 'POST' }),
@@ -144,7 +150,7 @@ async function refreshPlayerData(pdgaNumber) {
} catch (error) { } catch (error) {
console.error('Error refreshing player data:', error); console.error('Error refreshing player data:', error);
} finally { } finally {
if (icon) icon.classList.remove('spinning'); icons.forEach(icon => icon.classList.remove('spinning'));
} }
} }
+5
View File
@@ -65,6 +65,11 @@ function renderSparkline(values, opts) {
<span class="m-player-name"><%= player.name %></span> <span class="m-player-name"><%= player.name %></span>
<span class="m-pdga-num">#<%= player.pdgaNumber %></span> <span class="m-pdga-num">#<%= player.pdgaNumber %></span>
</div> </div>
<button class="icon-btn refresh-icon m-refresh-icon" type="button"
onclick="event.stopPropagation(); refreshPlayerData(<%= player.pdgaNumber %>)"
title="Refresh rating + prediction" aria-label="Refresh rating and prediction">
<i class="fas fa-sync-alt"></i>
</button>
<span class="m-chevron">&#9660;</span> <span class="m-chevron">&#9660;</span>
</div> </div>