feat(analytics): add manual achievement rebuild action
Signed-off-by: Marcus Fihlon <marcus@fihlon.swiss>
This commit is contained in:
parent
2c567a5e8e
commit
2ac3d82fda
2 changed files with 67 additions and 9 deletions
|
|
@ -133,6 +133,25 @@ public class AnalyticsController {
|
||||||
return ResponseEntity.ok(achievements);
|
return ResponseEntity.ok(achievements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild achievements for the authenticated user.
|
||||||
|
*/
|
||||||
|
@PostMapping("/achievements/rebuild")
|
||||||
|
public ResponseEntity<RebuildResponse> rebuildAchievements(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
|
||||||
|
UUID userId = getUserId(userDetails);
|
||||||
|
|
||||||
|
try {
|
||||||
|
achievementService.rebuildAchievementsForUser(userId);
|
||||||
|
return ResponseEntity.ok(new RebuildResponse("Achievements recalculated successfully"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to rebuild achievements for user {}", userDetails.getUsername(), e);
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body(new RebuildResponse("Failed to recalculate achievements: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get training load for a date range.
|
* Get training load for a date range.
|
||||||
*/
|
*/
|
||||||
|
|
@ -276,4 +295,6 @@ public class AnalyticsController {
|
||||||
case UNKNOWN -> "Not enough data to calculate form status.";
|
case UNKNOWN -> "Not enough data to calculate form status.";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record RebuildResponse(String message) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,15 @@
|
||||||
<h1>
|
<h1>
|
||||||
<i class="bi bi-award-fill" style="color: var(--accent-orange);"></i> Achievements
|
<i class="bi bi-award-fill" style="color: var(--accent-orange);"></i> Achievements
|
||||||
</h1>
|
</h1>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button id="rebuild-achievements-btn" type="button" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-arrow-repeat"></i> Recalculate
|
||||||
|
</button>
|
||||||
<a href="/analytics" class="btn btn-outline-primary">
|
<a href="/analytics" class="btn btn-outline-primary">
|
||||||
<i class="bi bi-arrow-left"></i> Back to Dashboard
|
<i class="bi bi-arrow-left"></i> Back to Dashboard
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Stats Summary -->
|
<!-- Stats Summary -->
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
|
|
@ -100,6 +105,35 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function rebuildAchievements() {
|
||||||
|
const rebuildBtn = document.getElementById('rebuild-achievements-btn');
|
||||||
|
const originalContent = rebuildBtn.innerHTML;
|
||||||
|
|
||||||
|
rebuildBtn.disabled = true;
|
||||||
|
rebuildBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Recalculating...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await FitPubAuth.authenticatedFetch('/api/analytics/achievements/rebuild', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.message || 'Failed to recalculate achievements');
|
||||||
|
}
|
||||||
|
|
||||||
|
FitPub.showAlert('success', result.message || 'Achievements recalculated successfully');
|
||||||
|
await loadAchievements();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error rebuilding achievements:', error);
|
||||||
|
FitPub.showAlert('danger', error.message || 'Failed to recalculate achievements');
|
||||||
|
} finally {
|
||||||
|
rebuildBtn.disabled = false;
|
||||||
|
rebuildBtn.innerHTML = originalContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateStats(achievements) {
|
function updateStats(achievements) {
|
||||||
// Earned count
|
// Earned count
|
||||||
document.getElementById('earned-count').textContent = achievements.length;
|
document.getElementById('earned-count').textContent = achievements.length;
|
||||||
|
|
@ -108,6 +142,8 @@
|
||||||
if (achievements.length > 0) {
|
if (achievements.length > 0) {
|
||||||
const latest = new Date(achievements[0].earnedAt);
|
const latest = new Date(achievements[0].earnedAt);
|
||||||
document.getElementById('latest-date').textContent = latest.toLocaleDateString();
|
document.getElementById('latest-date').textContent = latest.toLocaleDateString();
|
||||||
|
} else {
|
||||||
|
document.getElementById('latest-date').textContent = '-';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completion percentage
|
// Completion percentage
|
||||||
|
|
@ -217,6 +253,7 @@
|
||||||
window.location.href = '/auth/login';
|
window.location.href = '/auth/login';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
document.getElementById('rebuild-achievements-btn').addEventListener('click', rebuildAchievements);
|
||||||
loadAchievements();
|
loadAchievements();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue