many updates

This commit is contained in:
2026-01-11 06:06:43 -08:00
parent 67357b5038
commit 4bb86b603e
73 changed files with 2881 additions and 59 deletions

View File

@@ -56,6 +56,12 @@
<button class="btn btn-primary" id="download-btn">
<i class="bi bi-download"></i> Download
</button>
<button class="btn btn-outline-warning" id="refresh-btn" onclick="refreshActivity()">
<i class="bi bi-arrow-clockwise"></i> Refresh & Match
</button>
<button class="btn btn-warning" id="estimate-power-btn" onclick="estimatePower()">
<i class="bi bi-lightning-fill"></i> Estimate Power
</button>
<button class="btn btn-success" id="create-segment-btn" onclick="toggleSegmentMode()">
<i class="bi bi-bezier2"></i> Create Segment
</button>
@@ -228,7 +234,10 @@
<!-- Bike Info (New) -->
<div class="col-md-4">
<div class="card h-100 metric-card border-light shadow-sm">
<div class="card-header bg-light text-dark">Bike Setup</div>
<div class="card-header bg-light text-dark d-flex justify-content-between align-items-center">
<span>Bike Setup</span>
<button class="btn btn-sm btn-link p-0 text-decoration-none" onclick="editBikeSetup()">Edit</button>
</div>
<div class="card-body">
<div id="m-bike-info" class="text-center text-muted">No Setup</div>
</div>
@@ -647,6 +656,132 @@
setupDrag(endMarker, false);
}
async function refreshActivity() {
if (!confirm("Are you sure you want to re-download this activity from Garmin and run bike matching? This will overwrite any manual bike selection.")) {
return;
}
const btn = document.getElementById('refresh-btn');
const origHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Refreshing...';
// Helper toast fallback if showToast not defined in this view (it inherits from base.html usually?)
// base.html usually has showToast.
if (typeof showToast === 'function') showToast("Processing...", "Refreshing activity data...", "info");
try {
const res = await fetch(`/api/activities/${activityId}/redownload`, { method: 'POST' });
const data = await res.json();
if (res.ok) {
if (typeof showToast === 'function') showToast("Success", data.message, "success");
else alert(data.message);
// Reload details
loadDetails();
loadCharts();
} else {
throw new Error(data.detail || "Refresh failed");
}
} catch (e) {
console.error(e);
if (typeof showToast === 'function') showToast("Error", e.message, "error");
else alert("Error: " + e.message);
} finally {
btn.disabled = false;
btn.innerHTML = origHtml;
}
}
let allBikes = [];
async function fetchAllBikes() {
try {
const res = await fetch('/api/bike-setups');
if (res.ok) allBikes = await res.json();
} catch (e) { console.error(e); }
}
document.addEventListener('DOMContentLoaded', fetchAllBikes);
function editBikeSetup() {
const container = document.getElementById('m-bike-info');
if (container.querySelector('select')) return;
// Current text
const currentHtml = container.innerHTML;
let initialSelect = `<div class="input-group input-group-sm">
<select class="form-select" id="bike-select">
<option value="">-- No Bike --</option>`;
allBikes.forEach(b => {
initialSelect += `<option value="${b.id}">${b.name || b.frame} (${b.chainring}/${b.rear_cog})</option>`;
});
initialSelect += `</select>
<button class="btn btn-success" onclick="saveBikeSetup()"><i class="bi bi-check"></i></button>
<button class="btn btn-outline-secondary" onclick="loadDetails()"><i class="bi bi-x"></i></button>
</div>`;
container.innerHTML = initialSelect;
}
async function saveBikeSetup() {
const sel = document.getElementById('bike-select');
const newId = sel.value ? parseInt(sel.value) : null;
try {
const res = await fetch(`/api/activities/${activityId}/bike`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bike_setup_id: newId, manual_override: true })
});
if (res.ok) {
if (typeof showToast === 'function') showToast("Success", "Bike setup updated", "success");
else alert("Bike setup updated");
loadDetails();
} else {
const err = await res.json();
alert("Error: " + err.detail);
}
} catch (e) {
console.error(e);
alert("Save failed");
}
}
async function estimatePower() {
if (!confirm("Estimate power for this activity using physics usage calculation? This will update average/max power stats.")) return;
const btn = document.getElementById('estimate-power-btn');
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Estimating...';
try {
const res = await fetch(`/api/activities/${window.currentDbId}/estimate_power`, {
method: 'POST'
});
if (res.ok) {
const data = await res.json();
alert("Power estimation complete! Avg: " + data.stats.avg_power + " W");
loadDetails(); // Refresh stats
loadCharts(); // Refresh charts if stream updated (Service returns stream but we'd need to reload)
} else {
const err = await res.json();
alert("Error: " + err.detail);
}
} catch (e) {
console.error(e);
alert("Estimate failed: " + e.message);
} finally {
btn.disabled = false;
btn.innerHTML = origText;
}
}
async function saveSegment() {
if (startIndex >= endIndex) {
alert("Start point must be before End point.");