Files
FitTrack2/FitnessSync/backend/templates/bike_setups.html
2026-01-11 06:06:43 -08:00

233 lines
9.9 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Bike Setups</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addSetupModal">
<i class="bi bi-plus-lg"></i> Add Setup
</button>
</div>
<div class="card shadow-sm">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle" id="setupsTable">
<thead class="table-light">
<tr>
<th>Name</th>
<th>Frame</th>
<th>Chainring</th>
<th>Rear Cog</th>
<th>Gear Ratio</th>
<th>Rides</th>
<th>Distance</th>
<th>Weight</th>
<th>Active</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="setupsTableBody">
<!-- Populated by JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Add/Edit Modal -->
<div class="modal fade" id="addSetupModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="setupModalLabel">Add Bike Setup</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="setupForm">
<input type="hidden" id="setupId" name="id">
<div class="mb-3">
<label for="name" class="form-label">Name (Optional)</label>
<input type="text" class="form-control" id="name" name="name" placeholder="e.g. Track Bike">
</div>
<div class="mb-3">
<label for="frame" class="form-label">Frame</label>
<input type="text" class="form-control" id="frame" name="frame" required
placeholder="e.g. Dolan Pre Cursa">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="chainring" class="form-label">Chainring</label>
<input type="number" class="form-control" id="chainring" name="chainring" required min="1"
placeholder="Teeth">
</div>
<div class="col-md-6 mb-3">
<input type="number" class="form-control" id="rearCog" name="rear_cog" required min="1"
placeholder="Teeth">
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="weightKg" class="form-label">Weight (kg)</label>
<input type="number" class="form-control" id="weightKg" name="weight_kg" step="0.1" min="0"
placeholder="e.g. 9.0">
</div>
<div class="col-md-4 mb-3">
<label for="purchaseDate" class="form-label">Purchase Date</label>
<input type="date" class="form-control" id="purchaseDate" name="purchase_date">
</div>
<div class="col-md-4 mb-3">
<label for="retirementDate" class="form-label">Retired Date</label>
<input type="date" class="form-control" id="retirementDate" name="retirement_date">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="saveSetup()">Save</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentSetups = [];
const setupModal = new bootstrap.Modal(document.getElementById('addSetupModal'));
document.addEventListener('DOMContentLoaded', loadSetups);
async function loadSetups() {
try {
const response = await fetch('/api/bike-setups/');
if (!response.ok) throw new Error('Failed to load setups');
currentSetups = await response.json();
renderTable();
} catch (error) {
showToast(error.message, 'danger');
}
}
function renderTable() {
const tbody = document.getElementById('setupsTableBody');
tbody.innerHTML = '';
currentSetups.forEach(setup => {
const ratio = (setup.chainring / setup.rear_cog).toFixed(2);
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${setup.name || '-'}</td>
<td>${setup.frame}</td>
<td>${setup.chainring}t</td>
<td>${setup.rear_cog}t</td>
<td>${ratio}</td>
<td><span class="badge bg-secondary">${setup.activity_count || 0}</span></td>
<td>${setup.total_distance ? (setup.total_distance / 1000).toFixed(0) + ' km' : '-'}</td>
<td>${setup.weight_kg ? setup.weight_kg + 'kg' : '-'}</td>
<td>${setup.retirement_date ? '<span class="badge bg-danger">Retired</span>' : '<span class="badge bg-success">Active</span>'}</td>
<td>
<button class="btn btn-sm btn-outline-primary me-1" onclick="editSetup(${setup.id})">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteSetup(${setup.id})">
<i class="bi bi-trash"></i>
</button>
</td>
`;
tbody.appendChild(tr);
});
}
function resetForm() {
document.getElementById('setupForm').reset();
document.getElementById('setupId').value = '';
document.getElementById('setupModalLabel').textContent = 'Add Bike Setup';
}
// Hook into modal show event to reset form if adding
document.getElementById('addSetupModal').addEventListener('show.bs.modal', function (event) {
if (!event.relatedTarget || event.relatedTarget.getAttribute('data-bs-target')) {
// If triggered by button (not manual show for edit), reset
// Actually better to just check if we set an ID
}
});
// Reset on close
document.getElementById('addSetupModal').addEventListener('hidden.bs.modal', resetForm);
function editSetup(id) {
const setup = currentSetups.find(s => s.id === id);
if (!setup) return;
document.getElementById('setupId').value = setup.id;
document.getElementById('name').value = setup.name || '';
document.getElementById('frame').value = setup.frame;
document.getElementById('chainring').value = setup.chainring;
document.getElementById('rearCog').value = setup.rear_cog;
document.getElementById('weightKg').value = setup.weight_kg || '';
document.getElementById('purchaseDate').value = setup.purchase_date ? setup.purchase_date.split('T')[0] : '';
document.getElementById('retirementDate').value = setup.retirement_date ? setup.retirement_date.split('T')[0] : '';
document.getElementById('setupModalLabel').textContent = 'Edit Bike Setup';
setupModal.show();
}
async function saveSetup() {
const id = document.getElementById('setupId').value;
const data = {
name: document.getElementById('name').value || null,
frame: document.getElementById('frame').value,
chainring: parseInt(document.getElementById('chainring').value),
rear_cog: parseInt(document.getElementById('rearCog').value),
weight_kg: document.getElementById('weightKg').value ? parseFloat(document.getElementById('weightKg').value) : null,
purchase_date: document.getElementById('purchaseDate').value || null,
retirement_date: document.getElementById('retirementDate').value || null
};
if (!data.frame || !data.chainring || !data.rear_cog) {
showToast('Please fill in all required fields', 'warning');
return;
}
try {
const method = id ? 'PUT' : 'POST';
const url = id ? `/api/bike-setups/${id}` : '/api/bike-setups/';
const response = await fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error('Failed to save setup');
showToast('Setup saved successfully', 'success');
setupModal.hide();
loadSetups();
} catch (error) {
showToast(error.message, 'danger');
}
}
async function deleteSetup(id) {
if (!confirm('Are you sure you want to delete this setup?')) return;
try {
const response = await fetch(`/api/bike-setups/${id}`, { method: 'DELETE' });
if (!response.ok) throw new Error('Failed to delete setup');
showToast('Setup deleted', 'success');
loadSetups();
} catch (error) {
showToast(error.message, 'danger');
}
}
</script>
{% endblock %}